DEV Community

Anil Kumar
Anil Kumar

Posted on

How to Build a GraphQL API from Scratch in Node.js

Introduction

GraphQL has revolutionised API development by offering a flexible and efficient way to query data. Unlike REST APIs, where you often fetch unnecessary data or make multiple requests, GraphQL allows clients to request exactly what they need in a single request.

In this tutorial, we’ll build a GraphQL API from scratch using Node.js and Apollo Server. Our API will handle users and posts, providing a structured example of queries and mutations.

What We’ll Learn

  • Setting up a Node.js GraphQL server with Apollo Server
  • Defining a GraphQL schema (types, queries, and mutations)
  • Creating resolvers to handle requests
  • Connecting to MongoDB using Mongoose
  • Implementing JWT authentication

What is Apollo and why are we using it here? Apollo is a popular GraphQL server and client library. We use Apollo Server in Node.js to handle GraphQL queries, mutations, and subscriptions efficiently, providing features like schema stitching, caching, and middleware integration.

Let’s get started! 🚀

1️⃣ Setting Up the Project

Install Node.js & Initialise the Project
Ensure you have Node.js installed. Then, create a new project:

mkdir graphql-blog-api && cd graphql-blog-api
npm init -y
Enter fullscreen mode Exit fullscreen mode

Install Required Dependencies
We’ll use Apollo Server (for GraphQL), Express (for handling requests), and Mongoose (for MongoDB).

npm install express apollo-server-express graphql mongoose jsonwebtoken bcryptjs dotenv cors
Enter fullscreen mode Exit fullscreen mode

Why are we installing jsonwebtoken, bcryptjs, and cors?
jsonwebtoken — Used for authentication via JWT tokens.
bcryptjs — Used for securely hashing and comparing passwords.
cors — Enables Cross-Origin Resource Sharing (CORS), allowing the API to be accessed from different domains.

For development, install Nodemon:

npm install --save-dev nodemon
Enter fullscreen mode Exit fullscreen mode

Update package.json to enable auto-reloading:

"scripts": {
 "dev": "nodemon index.js"
}
Enter fullscreen mode Exit fullscreen mode

2️⃣ Defining the GraphQL Schema

GraphQL relies on a schema to define types and queries. Let’s create a schema.js file:

const { gql } = require("apollo-server-express");
// type defines a GraphQL object structure, and typedefs (short for type definitions) collectively define the GraphQL schema, including queries and mutations.
const typeDefs = gql`
    type User {
        id: ID!
        name: String!
        email: String!
        password: String!
    }
    type Post {
        id: ID!
        title: String!
        content: String!
        authorId: ID!
        author: User
    }
    type Query {
        users: [User]
        posts: [Post]
    }
    type Mutation {
        register(name: String!, email: String!, password: String!): User
        login(email: String!, password: String!): String
        createPost(title: String!, content: String!): Post
    }
`;
module.exports = typeDefs;
Enter fullscreen mode Exit fullscreen mode

What is gql?
gql (GraphQL Tag) is a template literal function from Apollo that parses GraphQL schema definitions into an AST (Abstract Syntax Tree).
What does ! mean in types?
The ! denotes a non-nullable field, meaning the field must have a value and cannot be null.

3️⃣ Setting Up Resolvers

Resolvers define how GraphQL responds to queries and mutations. Create resolvers.js:

const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const User = require("../models/User");
const Post = require("../models/Post");

const resolvers = {
  Query: {
    users: async () => await User.find(),
    posts: async () => await Post.find().populate("author"),
  },

  Mutation: {
    register: async (_, { name, email, password }) => {
      const hashedPassword = await bcrypt.hash(password, 10);
      return await User.create({ name, email, password: hashedPassword });
    },

    login: async (_, { email, password }) => {
      const user = await User.findOne({ email });
      if (!user || !(await bcrypt.compare(password, user.password))) {
        throw new Error("Invalid credentials");
      }
      return jwt.sign({ ...user.toObject() }, process.env.JWT_SECRET, { expiresIn: "1h" });
    },
    createPost: async (_, { title, content }, { user }) => {
      if (!user) throw new Error("Authentication required");
      const post = Post({ title, content, authorId: user._id });
      await post.save();
      return await Post.findById(post._id).populate('author');
    },
  },
};
module.exports = resolvers;
Enter fullscreen mode Exit fullscreen mode

4️⃣ Connecting to MongoDB

Create a .env file to store the MongoDB URI:

MONGO_URI=mongodb://localhost:27017/graphql_blog
JWT_SECRET="secretKey"
Enter fullscreen mode Exit fullscreen mode

Now, create models/User.js:

const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
    name: String,
    email: String,
    password: String,
});
module.exports = mongoose.model("User", userSchema);
Enter fullscreen mode Exit fullscreen mode

And models/Post.js:

const mongoose = require("mongoose");
const postSchema = new mongoose.Schema({
    title: String,
    content: String,
    authorId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
});

postSchema.virtual('author', {
    localField: 'authorId',
    foreignField: '_id',
    ref: 'User',
    justOne: true
})

module.exports = mongoose.model("Post", postSchema);
Enter fullscreen mode Exit fullscreen mode

5️⃣ Running & Testing the API

Create index.js to set up Apollo Server:

const express = require('express');
require('dotenv').config();
const { ApolloServer } = require('apollo-server-express');
const mongoose = require('mongoose');
const typeDefs = require("./src/graphql/schema");
const resolvers = require("./src/graphql/resolvers");

const app = express();

mongoose.connect(process.env.MONGO_URI)
  .then(() => console.log('connected to mongodb'))
  .catch((err) => console.log('Not able to connect to mongodb', err));

const jwt = require('jsonwebtoken');

const context = ({ req }) => {
  const authHeader = req.headers.authorization || '';
  const token = authHeader.split(' ')[1]; // Extract token from "Bearer <token>"

  if (token) {
    try {
      const user = jwt.verify(token, process.env.JWT_SECRET); // Decode user
      return { user }; // Attach user to context
    } catch (err) {
      console.log('Invalid Token');
    }
  }
  return {}; // No user attached if token is missing/invalid
};


const server = new ApolloServer({
  typeDefs,
  resolvers,
  context
});

async function startServer() {
  await server.start();
  server.applyMiddleware({ app });

  // Start Express Server
  app.listen(4000, () => {
    console.log(`Server running at http://localhost:4000${server.graphqlPath}`);
  });
}

startServer();
Enter fullscreen mode Exit fullscreen mode

Run the API:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Test queries in GraphQL Playground at http://localhost:4000/graphql.

Conclusion

🎉 You’ve built a GraphQL API from scratch using Node.js, Apollo Server, and MongoDB. You learned:
✅ How to define a GraphQL schema
✅ How to set up queries and mutations
✅ How to connect to MongoDB
✅ How to authenticate users

Next Steps: Add subscriptions for real-time updates or build a React frontend!

🚀 What would you like to learn next? Drop a comment below!

Top comments (0)