Using GraphQL Yoga with Fastify | Pure DI in TypeScript Node.js
Published: 2023-02-03 14:18:12

---
title: Using GraphQL Yoga with Fastify | Pure DI in TypeScript Node.js
published: true
date: 2023-02-03 01:42:24 UTC
tags: 
canonical_url: https://blog.vady.dev/using-graphql-yoga-with-fastify
---

In the [tutorial preceding this one](https://dev.to/vad3x/implementing-logger-with-metadata-24dj), I demonstrated how to create a logging service with metadata. In this article, I will be implementing a new GraphQL endpoint for our application.

> I will use [the repo](https://github.com/vad3x/typesafe-dependency-injection-in-typescript-samples/tree/main/samples/part3-logger) as starting point.

## Data Store

Before we start implementing the GraphQL server, let's make changes to our business logic layer by adding a new implementation for `FindOrderById`.

```ts
// order-store.ts
interface OrderStore {
  find(id: ID): Promise<Order | undefined>;
}

// find-order-by-id-from-store-provider.ts
export class FindOrderByIdFromStoreProvider {
  constructor(private readonly store: OrderStore) {}

  findOrderById: FindOrderById = (orderId) => {
    return this.store.find(orderId);
  };
}

// registry/find-order-by-id-from-store.ts
export function findOrderById() {
  return ({ orderStore }: { orderStore: OrderStore }) => {
    const { findOrderById } = new FindOrderByIdFromStoreProvider(orderStore);

    return {
      findOrderById,
    };
  };
}
```

In the code above I also introduced `OrderStore` interface. We can have multiple implementations for the interface, such as one making requests to SQL DB, or another making HTTP requests to a server.

Essentially, the `FindOrderById` is our business logic `primary` port, while `OrderStore` is a `secondary` port, in terms of [hexagonal architecture](https://alistair.cockburn.us/hexagonal-architecture/).

For now, let's implement the stub adapter for `OrderStore`:

```ts
// stub-order-store.ts
export class StubOrderStore implements OrderStore {
  constructor(private readonly logger: Logger) {}

  find(id: ID): Promise<Order | undefined> {
    this.logger.info(`calling StubOrderStore.find with orderId: ${id}`);

    return Promise.resolve({
      id,
    });
  }
}

// registry/stub-order-store.ts
export function orderStore() {
  return ({ createLogger }: { createLogger: CreateLogger }) => {
    const orderStore = new StubOrderStore(createLogger(StubOrderStore.name));

    return {
      orderStore,
    };
  };
}

// create-app-registry.ts
export function createAppRegistry() {
  return (
    new RegistryComposer()
      // ...
      .add(orderStore())
      .add(findOrderById())
      // ...
      .compose()
  );
}
```

## GraphQL Server

Since I was using `fastify` I'm going to integrate it with [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server/docs/integrations/integration-with-fastify) because of simple integration.
First of all, we need to install some dependencies:

```sh
yarn install graphql graphql-yoga dataloader
yarn install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-resolvers ts-node
```

Now we can add `graphqlRoutes` `FastifyPlugin`:

```ts
// routes/graphql.ts
type HandleNodeRequest = (
  nodeRequest: NodeRequest
) => Promise<Response> | Response;

export function graphqlRoutes({
  handleGraphQLNodeRequest,
}: {
  handleGraphQLNodeRequest: HandleNodeRequest;
}): FastifyPluginCallback {
  return (fastify, _, next) => {
    fastify.route({
      url: "/graphql",
      method: ["GET", "POST", "OPTIONS"],
      handler: async (req, reply) => {
        const response = await handleGraphQLNodeRequest(req);

        response.headers.forEach((value, key) => {
          reply.header(key, value);
        });

        reply.status(response.status);

        reply.send(response.body);

        return reply;
      },
    });

    next();
  };
}
```

Here we added a simple route for any HTTP method, that just forwards the request to `graphqlYoga.handleNodeRequest`.

Let's create the route registrar now:

```ts
// registry/graphql-yoga.ts
export function graphqlYoga<
  TServerContext extends Record<string, any> = object,
  TUserContext extends Record<string, any> = object
>(options?: Omit<YogaServerOptions<TServerContext, TUserContext>, "schema">) {
  return ({
    createLogger,
    graphqlSchema,
  }: {
    createLogger: CreateLogger;
    graphqlSchema: GraphQLSchemaWithContext<TUserContext & TServerContext>;
  }) => {
    const graphqlYoga = createYoga<TServerContext, TUserContext>({
      ...options,
      logging: createLogger("GraphQLYoga"),
      schema: graphqlSchema,
    });

    return {
      handleGraphQLNodeRequest: graphqlYoga.handleNodeRequest.bind(graphqlYoga),
    };
  };
}
```

and add it to our `fastifyServer`:

```ts
// registry/fastify-server.ts
export function fastifyServer() {
  return (
    deps: Parameters<typeof runWithRequestIdPlugin>[0] &
      Parameters<typeof ordersRoutes>[0] &
      Parameters<typeof graphqlRoutes>[0]
  ) => {
    const server = Fastify({});

    server.register(runWithRequestIdPlugin(deps));
    server.register(ordersRoutes(deps));
    server.register(graphqlRoutes(deps));

    return {
      fastifyServer: server,
    };
  };
}
```

## GraphQL Schema

Now we can register our GraphQL schema:

```gql
# schema.gql
type Order {
  id: ID!
}

type Query {
  order(id: ID!): Order
}
```

> Using `graphql-codegen` we can generate the typescript definitions from the schema file, [see config here](https://github.com/vad3x/typesafe-dependency-injection-in-typescript-samples/blob/main/samples/part4-dataloader/codegen.yml).

```ts
// graphql-schema.ts

import { Resolvers } from "./generated/resolvers-types";

export function graphqlSchema() {
  return ({ findOrderById }: { findOrderById: FindOrderById }) => {
    return {
      graphqlSchema: createSchema({
        typeDefs: fs.readFileSync(
          path.join(__dirname, "../schema.gql"),
          "utf8"
        ), // <-- I read schema from file here, but we can also use [esbuild loader](https://github.com/luckycatfactory/esbuild-graphql-loader)
        resolvers: {
          Query: {
            order: (_, args) => findOrderById(args.id),
          },
        } as Resolvers,
      }),
    };
  };
}
```

```ts
// create-app-registry.ts
export function createAppRegistry() {
  return (
    new RegistryComposer()
      // ...
      .add(graphqlSchema())
      .add(graphqlYoga())
      /// ...
      .compose()
  );
}

// index.ts
async function main() {
  const registry = createAppRegistry();

  const server = Fastify({});
  server.register(runWithRequestIdPlugin(registry));
  server.register(ordersRoutes(registry));
  server.register(graphqlRoutes(registry));

  try {
    await server.listen({ port: PORT });

    console.log("listening on port", PORT);
  } catch (err) {
    server.log.error(err);
    process.exit(1);
  }
}
```

Let's run the app now and try to hit our new `/graphql` endpoint

```http
POST /graphql HTTP/1.1
Host: localhost:3000
Content-Type: application/json
Content-Length: 80

{"query":"query {\n  order(id: \"order-0\") {\n      id\n  }\n}","variables":{}}
```

Response:

```json
{
  "data": {
    "order": {
      "id": "order-0"
    }
  }
}
```

> Full example source code can be found [here](https://github.com/vad3x/typesafe-dependency-injection-in-typescript-samples/blob/main/samples/part4-graphql)

## Conclusion

In this article, we learned how to add the `GraphQL Yoga` plugin to `Fastify` using a Pure Dependency Injection registry.
In the next article, I'll cover a more interesting topic: how to use `DataLoader` with `GraphQL Yoga` and `AsyncLocalStorage`.