Skip to content

"The only logic in my server-side codebase should be application-specific business logic." to an extreme? sort of

License

Notifications You must be signed in to change notification settings

aldahick/athena

Repository files navigation

athena

NPM version badge Core workflow status badge Demo workflow status badge React Utils workflow status badge Utils workflow status badge

A modular backend framework atop Apollo and Fastify. Simpler and more opinionated than its inspiration, Nest.

If you're curious about recent changes, please see the changelog.

Features

To Do

Getting Started

Install dependencies:

npm install --save @athenajs/core reflect-metadata
npm install --save-dev @types/node typescript

Make sure your tsconfig has "experimentalDecorators": true and "emitDecoratorMetadata": true. To use ES modules, either set "type": "module" in your package.json, or name your files .mts instead of .ts.

Instantiate and start the server:

import "reflect-metadata";
import { createApp } from "@athenajs/core";
import "./config.js";
import "./hello-resolver.js";

const app = createApp();
await app.start();

Since Athena relies on dependency injection rather heavily, you accomplish configuration by extending BaseConfig. The following code is the same ./config.js imported above. (Note the additional dependency on @athenajs/utils, for convenience's sake.)

import { BaseConfig, config } from "@athenajs/core";
import { getModuleDir } from "@athenajs/utils";
import { resolve } from "path";

@config()
export class Config extends BaseConfig {
  // for example, to specify the directories containing your GQL schema, override like so:
  readonly graphqlSchemaDirs = [resolve(getModuleDir(import.meta), "../schema")];
  // or, for new fields altogether, you can use this.optional & this.required to read/verify environment variables
  readonly databaseUrl: string = this.required("DATABASE_URL");
  readonly environment: string | undefined = this.optional("NODE_ENV");
}

It's simple to inject dependencies into constructors, thanks to tsyringe. Writing resolvers is just as straightforward:

/**
 * resolves for the following schema:
 * type Query {
 *   hello: String!
 * }
 */
import { resolver } from "@athenajs/core";
import { Config } from "./config.js";

@resolver()
export class HelloResolver {
  // inject dependencies by including them as constructor params
  constructor(private config: Config) { }

  @resolveQuery()
  async hello(): Promise<string> {
    return "Hello, world!";
  }

  // If you don't want to name your methods after the fields they resolve, don't!
  @resolveField("Query.hello")
  async resolveHello(): Promise<string> {
    return `Hello, resolved world! Running in environment: ${this.config.environment}`;
  }
}

See the demo package for a complete example, including HTTP routes.

Development

We use the Node test runner, so make sure to install Node.JS v18.13 / v19.2 or higher.

To publish new versions, run pnpm version <new-version> in the appropriate package directory; a new Git tag will be pushed, and the CI will publish the package automatically (see statuses here).

About

"The only logic in my server-side codebase should be application-specific business logic." to an extreme? sort of

Resources

License

Stars

Watchers

Forks

Packages

No packages published