close icon
daily.dev platform

Discover more from daily.dev

Personalized news feed, dev communities and search, much better than what’s out there. Maybe ;)

Start reading - Free forever
Continue reading >

Building modern APIs with Fastify, Graphql, and MongoDB

Building modern APIs with Fastify, Graphql, and MongoDB
Author
Lawrence Eagles
Related tags on daily.dev
toc
Table of contents
arrow-down

🎯

Both Fastify and GraphQL are great technologies that offer improved speed, flexibility, and easy code maintainability.

Introduction

Fastify is a plugin-based, highly efficient and, developer-friendly Nodejs framework inspired by Hapi and Express. However, it offers better performance with low overhead. Fastify shines when it comes to building fast HTTP server, and benchmark shows that in some cases it performs nearly three times faster than Express.

Also, Fastify uses an extremely fast radix tree, based HTTP router called find-my-way which performs more operations per second compared to its alternatives. Here is benchmark for the most commonly used routers. Fastify is a very modular system that is fully extensible with hooks, plugins and, decorators. In this article, we would be looking at how to integrate Fastify with GraphQL.

GraphQL

GraphQL — Graph Query Language,  is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL creates fast and flexible APIs and gives the client complete control to describe what data they need — as opposed to traditional REST API endpoints where the server determines what is returned. Consequently, with GraphQL, we get fewer HTTP requests, flexible data querying, and less code to manage. Also, like REST, GraphQL operates over HTTP, so we can use it with any database, backend language, or client.

In this article, we would be building APIs for a blog using Fastify and GraphQL.

Out of the box, Fastify does not support GraphQL but we can easily add GraphQL support by using the mercurius plugin. Mercurius is a GraphQL adapter for Fastify; it provides GraphQL integration with caching of both query parsing and validation. Also, it provides a Just-In-Time compiler via graphql-jit and an automatic loader integration to avoid 1 + N queries. We will learn more about mercurius as we build our blog APIs; in the subsequent section. Let’s get started with the prerequisites.

Prerequisite

To follow along in this article, here are a few prerequisites to note:

  1. Node.js and MongoDB installed.
  2. Basic knowledge of MongoDB and Mongoose.
  3. Basic knowledge of JavaScript & GraphQL.

Getting Started

To get started, we need to set up our server.

Follow the steps below to create the server:

  1. Create an npm project and install all the needed dependencies:

# create project directory
mkdir <!--- project name -->
cd <!-- project name -->

# create npm project
npm init -y

# install dependencies
npm i fastify nodemon fastify-plugin mercurius  mongoose

  1. Enable es6 modules:

While tools like Babel have been long used to achieve this, we will use simpler solutions in this article. Simply add the following code to your package.json file — if you have Node version 12 and above installed.


"type": "module"

However, if you have Node version 8 or 10 you can enable this feature by using esm.

  1. Add our scripts:

Open the package.json file and edit the scripts section as follows:


...
"scripts": {
    "start": "node --es-module-specifier-resolution=node ./src/index.js",
    "dev": "nodemon --es-module-specifier-resolution=node ./src/index.js"
}
...

  • "start": "node --es-module-specifier-resolution=node src/index.js" is the production command to start our server it uses node.
  • "dev": "nodemon --es-module-specifier-resolution=node ./src/index.js" uses nodemon to restart our server any time we compile our code.

The --es-module-specifier-resolution=node is required to aid interoperability between the es module — ESM and Node’s commonJS modules. The current ESM specifier resolution does not support all the features of the commonJS loader. A key difference is the automatic resolution of file extensions and the ability to import directories that have an index file.

However, we can customize the ESM specifier resolution algorithm by adding the --es-module-specifier-resolution=node flag.

  1. Setup our server:

In the root directory, create a src directory with an index.js file containing the following code:


import fastify from 'fastify';
import mercurius from 'mercurius';

const Port = process.env.PORT || 4500;
const uri = process.env.MONGODB_URI || 'mongodb://localhost:27017/graphql-blog';

const app = fastify({ logger: true });

// Activate plugins below:

// create server
const start = async () => {
    try {
        await app.listen(Port);
    } catch (err) {
        app.log.error(err);
        process.exit(1);
    }
};
start();

Now we can start the server by running npm run dev the outputs:


{"level":30,"time":1620202591072,"pid":11775,"hostname":"pc-name","msg":"Server listening at http://127.0.0.1:4500"}

Great our server is working. Let’s start building our blog APIs with GraphQL in the next section.

Building GraphQL APIs

To get started we need to build our data models and connect our application to MongoDB.

Create Models

In the src directory create a config directory with an index.js file and a models, directory. Inside the models directory, create a post.js file with the following codes:


import mongoose from 'mongoose';
const post = mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    body: {
        type: String,
        required: true
    },
    category: {
        type: String,
        required: true
    },
    published: {
        type: Boolean,
        required: true
    }
});
const Post = mongoose.model('post', post);
export default Post;

Connecting to MongoDB using Mongoose

In the index.js file in the config directory add the following code:


import fp from 'fastify-plugin';
import mongoose from 'mongoose';
import Post from '../models/post';
const models = { Post };
const ConnectDB = async (fastify, options) => {
    try {
        mongoose.connection.on('connected', () => {
            fastify.log.info({ actor: 'MongoDB' }, 'connected');
        });
        mongoose.connection.on('disconnected', () => {
            fastify.log.error({ actor: 'MongoDB' }, 'disconnected');
        });
        const db = await mongoose.connect(options.uri, {
            useNewUrlParser: true,
            useUnifiedTopology: true,
            useCreateIndex: true
        });
        // decorates fastify with our model
        fastify.decorate('db', { models });
    } catch (error) {
        console.error(error);
    }
};
export default fp(ConnectDB);

The code above connects our app to MongoDB using mongoose and logs the connection status to the console. Also, we decorate Fastify with our models to foster code reuse. After creating the Fastify plugin above, to connect our application to MongoDB, we need to create our GraphQL schema and resolvers.

Setup GraphQL schema and resolvers

In other to add GraphQL support to our app, mercurius takes two plugin options namely schema, resolvers, and graphiql — you can get a complete list of plugin options here. The schema option is a string — that would be parsed or the schema definition. It takes our GraphQL schema as its value. The resolvers option is an object that takes our resolvers as its value. The graphiql option takes a String or a Boolean. It would serve GraphiQL on /graphiql if true. Also, we can pass a string Playground to serve GraphiQL on /playground. Inside the src directory create a graphql folder and create a schema.js file in it.

Add the following code to the schema.js file:


const schema = `
type Query {
    post(id: ID!): Post!
    posts: [Post]!
}
type Mutation {
    createPost(data: CreatePostInput!): Post!
}
type Post {
    id: ID!
    title: String!
    body: String!
    category: String!
    published: Boolean!
}
input CreatePostInput {
    id: ID
    title: String!
    body: String!
    category: String!
    published: Boolean!
}
`;
export default schema;

The code above contains our GraphQL schema.

Also, create a resolvers.js file inside the graphql folder and add the following code to it:


import Post from '../models/post';
const resolvers = {
    Query: {
        posts: async (_, obj) => {
            const posts = await Post.find({});
            return posts;
        },
        post: async (_, obj) => {
            const { id } = obj;
            const post = await Post.findById(id);
            return post;
        }
    },
    Mutation: {
        createPost: async (_, { data }) => {
            const newPost = new Post(data);
            const post = await newPost.save();
            return post;
        }
    }
};
export default resolvers;

Resolvers are functions that are responsible for populating the data for a single field in our schema. Resolvers respond to client requests by fetches the requested data from the appropriate data source. Our resolvers above take two objects as parameters — - and obj. Our query arguments are available in the obj parameter hence we can destructure our data from them as seen in our Mutation.

Finally, to get all this working, we need to register our Fastify plugins.

Register Fastify Plugins

Import in the index.js file, we import all our Fastify plugins as seen below:


...
import db from './config/index';
import schema from './graphql/schema';
import resolvers from './graphql/resolvers';
...

Then we active these plugins by adding the following codes below the
// Active plugins below:


app.register(db, { uri });
app.register(mercurius, {
    schema,
    resolvers,
    graphiql: 'playground'
});

The code above registers our db plugin which connects our app to MongoDB. And also, registers the mercurius plugin which adds GraphQL support to our application.

Now when we can test our server by running:


#start mongodb
mongod // in linux you may need to run sudo mongod

#start dev server
npm run dev

we get:


{"level":30,"time":1620203117327,"pid":13631,"hostname":"pc-name","actor":"MongoDB","msg":"connected"}

{"level":30,"time":1620203117389,"pid":13631,"hostname":"pc-name","msg":"Server listening at http://127.0.0.1:4500"}

Also, we can access the GraphQL playground to test our APIs by visiting http://localhost:4500/playground with our browser.

Testing Queries

In this article, we would test our GraphQL APIs using curl and jq.

Query: Posts:


curl 'http://localhost:4500/graphql' -X POST -H 'content-type: application/json' -d '{"query":"{posts {id title body category published} }"}' | jq

// returns 
{
  "data": {
    "posts": [
      {
        "id": "6091437466370422dd8b43af",
        "title": "Getting Started With Fastify and Graphql",
        "body": "GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.",
        "category": "nodejs",
        "published": true
      }
    ]
  }
}


Query Post:


curl 'http://localhost:4500/graphql' -X POST -H 'content-type: application/json' -d '{"query":"{post(id:\"6091437466370422dd8b43af\") {id title body category published} }"}' | jq

//returns
{
  "data": {
    "post": {
      "id": "6091437466370422dd8b43af",
      "title": "Getting Started With Fastify and Graphql",
      "body": "GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.",
      "category": "nodejs",
      "published": true
    }
  }
}

Mutation:


curl 'http://localhost:4500/graphql' -X POST -H 'content-type: application/json' \
-d '{"query":"mutation {createPost(data: {title: \"Getting Started With Fastify & Graphql\", body: \"GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.\", category: \"nodejs\", published: true}) {id title body category published} }"}' | jq

// returns:
{
  "data": {
    "createPost": {
      "id": "60914e6fc90134236e0a131c",
      "title": "Getting Started With Fastify & Graphql",
      "body": "GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.",
      "category": "nodejs",
      "published": true
    }
  }
}

Conclusion

Both Fastify and GraphQL are great technologies that offer improved speed, flexibility, and easy code maintainability. The Fastify GraphQL adapter mercurius adds GraphQL support to Fastify. It also supports TypeScript by using TypeGraphQL. This allows you to follow a code-first approach instead of the SDL first.

A full working example of our blog APIs is available on Github. And I hope after this article you are ready to start building modern APIs with Fastify and GraphQL.

Why not level up your reading with

Stay up-to-date with the latest developer news every time you open a new tab.

Read more