DEV Community

Cover image for Fastify GraphQL API Stack: with Mercurius & Nexus
Rodney Lab
Rodney Lab

Posted on • Originally published at rodneylab.com

Fastify GraphQL API Stack: with Mercurius & Nexus

📈 Why Work on a New GraphQL API Stack?

Here I talk a little about a Fastify GraphQL API stack I have assembled and some of the choices made. This stack is something I will use as a staring point for new node backends which I want to interface with using GraphQL queries. I had previously used something based on a 14-hour Ben Awad YouTube tutorial. That tutorial turns two years old this week and has amassed an impressive 1.1 million views over that time. However, two years is a long time in tech and over that time I have swapped out parts as newer tools got introduced. The latest change saw me swap out TypeGraphQL for Nexus. TypeGraphQL was just about one of the only remaining original components and needed a bit of refactoring. For those reasons it seemed a good time to put together a fresh demo repo.

The original stack was Node Express, TypeScript, Apollo, PostgreSQL, TypeORM and TypeGraphQL. My latest incarnation still has TypeScript and PostgreSQL but uses Fastify as a node server, Mercurius (from the Fastify team) as the GraphQL server, Prisma (replacing TypeORM) and Nexus instead of TypeGraphQL. I also use uvu for unit testing. I’ll talk a little about what the elements bring to the party, but if you just want to see the code, its on the Rodney Lab GitHub repo (link further down the page).

🧑🏽‍🦼 Fastify

Fastify forms the foundations of the stack. It is built to be a highly performant node server and an alternative to Express. As well as being fast, fastify has an extensive ecosystem of over 200 plugins from the community and core team. Ones you are likely to use most often are @fastify/cookie, @fastify/redis and @fastify/session. These are generally pretty quick to integrate and have minimal configuration. One consideration to take account of though is that Fastify and the plugins are always being improved. That means it can take a while to dust off a project you haven’t touched in a while and bring it up-to-date.

Fastify DX is in the pipeline. It brings Vite-powered front end solutions in Svelte, React and other frameworks to Fastify and is already in alpha. This will let you create full stack apps using Fastify together with your preferred frontend.

🌡 Mercurius

I was using Apollo GraphQL server previously but opted for Mercurius for tighter Fastify integration. It was a GraphiQL web frontend like Apollo, which is handy for debugging your queries.

Fastify GraphQL API Stack: screen capture shows GraphiQL with query in left pane, response in the middle and schema docs on the right

Mercurius also has its own testing integration for integration testing.

⛓ Nexus

Nexus is kind of glue for your database and GraphQL. It plays a similar role as TypeGraphQL; helping you generate GraphQL schemas from your TypeScript models for your apps entities. You use the generates schema to initialise your GraphQL server. The way for coding up your entities is quite different, comparing TypeGraphQL to Nexus; switching involved a bit of refactoring. With TypeGraphQL you define resolvers (functions detailing what data to respond to each GraphQL query or mutation with) as classes and decorate them with your predefined GraphQL types.

Here’s the Nexus resolver for a basic Hello query. It just responds with the text Hello everybody!. Typically, though, you will take in input from the query, process it, perhaps checking for a logged in user and then querying your database to form the response data.

import { extendType } from 'nexus';

export const HelloQuery = extendType({
    type: 'Query',
    definition(t) {
        t.field('hello', {
            type: 'String',
            resolve() {
                return 'Hello everybody!';
            },
        });
    },
});
Enter fullscreen mode Exit fullscreen mode

I like how Nexus outputs a GraphQL schema file (schema.graphl) into you project folder and also a TypeScript file with all the types generated from your schema (nexus-typegen.ts in the repo). This all comes from the resolver and entity model definitions which you provide.

### This file was generated by Nexus Schema
### Do not make changes to this file directly


type Mutation {
  createDraft(body: String!, title: String!): Post!
  deleteDraft(draftId: Int!): Boolean!
  publish(draftId: Int!): Post!
}

type Post {
  body: String
  id: Int
  published: Boolean
  title: String
}

type Query {
  drafts: [Post]!
  hello: String
  posts: [Post]
}
Enter fullscreen mode Exit fullscreen mode

It took a bit of getting used to and the Nexus tutorial was super helpful. That uses Apollo Server, Jest, and SQLite with Prisma to build out a basic GraphQL API for creating and editing blog posts. I followed along but added a few extra queries to cement my understanding and used Mercurius, uvu and PostgreSQL instead of Apollo, Jest and SQLite. The code in the repo is based on the tutorial but with this new stack and a few extra queries.

What does Prisma Bring to the Party?

Prisma is fantastic for working with databases in your projects. You create a schema for your database (can be PostgreSQL, SQLite or others) and it generates TypeScript types for you and a set of utility functions for CRUD operations. The schema is a single file listing the your database entities and their relations. The Prisma VSCode extension helps create and format it. Prisma also helps generate migration files whenever you update your database schema. I use it with Remix sites as well as API projects. Kent C Dodds is a Prisma fan too! Give it a spin on your next pet project if you haven’t yet tried it out.

uvu Testing

uvu is a fast test runner. There’s another article on setting up uvu with Fastify so we won’t go into it in detail here. It has a much lighter footprint than Jest and sits in the same class as Vitest. You can also use uvu to unit test UI components, in Svelte for example. In the repo we have a little helper function within a test context which spins up a new Fastify instance for testing. It then takes GraphQL queries as an input, converting to them to fetch requests and injecting them into that live Fastify instance.

export function createTestContext(): TestContext {
    const ctx = {} as TestContext;
    let serverInstance: FastifyInstance | null = null;
    const prismaCtx = prismaTestContext();
    test.before(async () => {
        serverInstance = await build({ logger: false });
    });

    test.before.each(async (meta) => {
        console.log(meta.__test__);
        const db = await prismaCtx.before();

        async function request(query: string, variables = {} as Record<string, string>) {
            return serverInstance?.inject({
                method: 'POST',
                url: 'graphql',
                headers: { 'content-type': 'application/json' },
                payload: { query, variables },
            });
        }

        Object.assign(ctx, { db, request });
    });
Enter fullscreen mode Exit fullscreen mode

The repo code includes a docker-compose.yml file with two PostgreSQL instances, one for dev and another for testing:

services:
  postgres:
    image: postgres:14.4
    restart: always
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: fastify
    volumes:
      - postgres:/var/lib/postgresql/data
    ports:
      - '5432:5432'

  postgres-test:
    image: postgres:14.4
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: fastify
    ports:
      - '5435:5432'

volumes:
  postgres:
Enter fullscreen mode Exit fullscreen mode

Notice we omit the volumes field on the test instance because we do not need data to persist in the test environment. As well as this, the second instance listens on a different port (5435), but connects to 5432 on your machine. For this to work, we have two .env files and set DATABASE_URL in the test one to use port 5435:

# Environment variables declared in this file are automatically made available to Prisma.

# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.

# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="postgresql://postgres:postgres@localhost:5435/fastify?schema=public"
Enter fullscreen mode Exit fullscreen mode

🙌🏽 Fastify GraphQL API Stack: Wrapping Up

We took a look at an up-to-date Fastify GraphQL API stack and some reasoning for the choices made. In particular, we saw:

  • some of the plugins in the Fastify ecosystem,
  • what Nexus is and some differences to TypeGraphQL,
  • some code snippets form the stack, like injecting GraphQL queries into a running Fastify server for unit testing.

Please take a look at the stack code if you haven’t yet. It’s in the RodneyLab GitHub repo. I hope you find it useful for your own projects. Keen to hear your feedback on how it can be further modernised. Especially if you know new related tools which I have not included.

🙏🏽 Fastify GraphQL API Stack: Feedback

Have you found the post useful? Would you prefer to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a few dollars, euros or pounds, then please consider supporting me through Buy me a Coffee.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter and also askRodney on Telegram. Also, see further ways to get in touch with Rodney Lab. I post regularly on Astro as well as SvelteKit. Also subscribe to the newsletter to keep up-to-date with our latest projects.

Top comments (0)