Stop 09 · API DesignProjects · 1/2

GraphQL API

Part of the Backend Developer Roadmap

In short

Time to build a real one: a bookstore GraphQL API with authors, books and reviews. You'll hit the classic problems, nested data, N+1 queries and messy errors, and fix each one yourself.

A GraphQL API for a real domain (e.g. a bookstore or blog) — practice for schema design, resolvers, and handling nested/related data efficiently.

The blob character ties strings between books and author cards at a bookstore counter.

Think of it like this

Running the library desk

A good librarian knows which books belong to which author, and which reviews belong to which book. Ask for 'Premchand's books with their reviews' and she brings it all in one trip. Your API plays librarian: one query, linked data, nothing extra.

Watch it happen

Build plan

Schema
Resolvers
Database
DataLoader
Mutations
Errors

1/6Write the SDL: Author, Book, Review, and a Query type with books and book(id).

The big ideas

What you need to know

A clean schema

Types for Author, Book and Review, non-null where data always exists, and input types for mutations.

No N+1 queries

Listing 20 books with authors should run 2 database queries, not 21. DataLoader makes that happen.

Mutations that validate

addReview should reject a rating outside 1 to 5 with a clear error, not a crash.

Paginated lists

books(first: 10, after: cursor) keeps big lists manageable, just like REST pagination.

See it in code

Walk through the code

ts
1import DataLoader from "dataloader";2 3export function createLoaders() {4  return {5    author: new DataLoader(async (ids: readonly string[]) => {6      const rows = await db.authors.findMany({ where: { id: { in: [...ids] } } });7      const byId = new Map(rows.map((a) => [a.id, a]));8      return ids.map((id) => byId.get(id) ?? null);9    }),10  };11}12 13// In resolvers, with fresh loaders put on context for every request:14const resolvers = {15  Book: { author: (book, _args, ctx) => ctx.loaders.author.load(book.authorId) },16};

1/3DataLoader collects every author id asked for in one tick and fetches them in a single query.

Create loaders per request inside your context function, so one user's cached data never leaks into another user's request.

Save yourself the pain

Common mistakes

Oops

Sharing one DataLoader instance across all requests.

Do this instead

Its cache would serve stale data, or another user's data. Create fresh loaders for every request.

Oops

Designing the schema as a copy of your database tables.

Do this instead

Design for what screens need. Hide join tables and internal columns behind clean types.

Your mission

First milestone: books and authors

~45 min

Quick check

Twenty books are listed, each with its author. Without batching, how many database queries run?

Go deeper

  1. 1Design a schema covering at least two related types with nested queries.
  2. 2Implement a mutation alongside your queries, e.g. create a review.
  3. 3Add DataLoader to batch and cache resolver calls, avoiding N+1 queries.

More projects at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge