Time to stop reading and start running code. In this lesson we build a complete, working GraphQL server in Node.js using Apollo Server 4: schema, resolvers, in-memory data, and mutations. By the end you will have a playground at http://localhost:4000 answering real queries.
mkdir graphql-blog && cd graphql-blog
npm init -y
npm install @apollo/server graphqlAdd "type": "module" to package.json so we can use ES imports. Apollo Server 4 is a lean library: @apollo/server plus the reference graphql implementation is all we need. (Alternatives like graphql-yoga or Mercurius for Fastify follow the same shape; skills transfer directly.)
Create index.js and start with type definitions, the same SDL from earlier lessons:
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
const typeDefs = `#graphql
type User { id: ID!, name: String!, posts: [Post!]! }
type Post { id: ID!, title: String!, published: Boolean!, author: User! }
type Query {
users: [User!]!
post(id: ID!): Post
}
type Mutation {
createPost(title: String!, authorId: ID!): Post!
}
`;The #graphql comment tag enables syntax highlighting in most editors.
For learning purposes an in-memory store is perfect; swapping in Postgres or Mongo later only changes resolver bodies, never the schema:
const users = [
{ id: "1", name: "Ada" },
{ id: "2", name: "Grace" },
];
const posts = [
{ id: "1", title: "On Engines", published: true, authorId: "1" },
{ id: "2", title: "Compilers 101", published: true, authorId: "2" },
];
const resolvers = {
Query: {
users: () => users,
post: (_, { id }) => posts.find((p) => p.id === id) ?? null,
},
Mutation: {
createPost: (_, { title, authorId }) => {
const post = { id: String(posts.length + 1), title, published: false, authorId };
posts.push(post);
return post;
},
},
User: {
posts: (user) => posts.filter((p) => p.authorId === user.id),
},
Post: {
author: (post) => users.find((u) => u.id === post.authorId),
},
};Note how User.posts and Post.author bridge the foreign key: the raw records store authorId, but the API exposes real object edges.
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
console.log(`Server ready at ${url}`);Run node index.js, open http://localhost:4000, and Apollo Sandbox loads with your schema fully introspected. Try:
mutation { createPost(title: "Hello GraphQL", authorId: "1") { id title } }then query { users { name posts { title } } } to see the new post attached to Ada.
context.typeDefs and resolvers into modules per domain as the schema grows.createPost.typeDefs, resolvers, and startStandaloneServer.Next lesson: Fragments and Reusable Query Parts, a client-side technique for keeping large queries maintainable.