Skip to content
SP StackPractices
intermediate

Implement a GraphQL API

Build a production-ready GraphQL API with type-safe schemas, resolvers, and query optimization in Python, JavaScript, and Java.

Topics: api

Implement a GraphQL API

Overview

GraphQL is a query language and runtime for APIs that allows clients to request exactly the data they need. Unlike REST, where the server defines the response structure, GraphQL puts the client in control — reducing over-fetching and under-fetching while providing strong typing through schemas.

This recipe covers building a production-ready GraphQL API with type-safe schemas, resolvers, mutations, and subscriptions across Python, JavaScript, and Java.

When to Use

Use this resource when:

  • Your clients need flexible data fetching (mobile apps with limited bandwidth)
  • You want strongly typed API contracts with automatic documentation
  • You need to aggregate data from multiple microservices
  • Your API consumers request different field combinations frequently

Solution

Python

import strawberry
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter

@strawberry.type
class Book:
    title: str
    author: str
    pages: int

@strawberry.type
class Query:
    @strawberry.field
    def books(self) -> list[Book]:
        return [
            Book(title="Clean Code", author="Robert C. Martin", pages=464),
            Book(title="The Pragmatic Programmer", author="Andy Hunt", pages=352),
        ]

schema = strawberry.Schema(query=Query)
app = FastAPI()
app.include_router(GraphQLRouter(schema), prefix="/graphql")

JavaScript

const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type Book {
    title: String!
    author: String!
    pages: Int!
  }

  type Query {
    books: [Book!]!
  }
`;

const resolvers = {
  Query: {
    books: () => [
      { title: 'Clean Code', author: 'Robert C. Martin', pages: 464 },
      { title: 'The Pragmatic Programmer', author: 'Andy Hunt', pages: 352 },
    ],
  },
};

const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => console.log(`Server ready at ${url}`));

Java

import com.coxautodev.graphql.tools.GraphQLQueryResolver;
import graphql.servlet.SimpleGraphQLHttpServlet;
import javax.servlet.annotation.WebServlet;

public class Book {
    private String title;
    private String author;
    private int pages;
    // getters and setters
}

public class QueryResolver implements GraphQLQueryResolver {
    public List<Book> books() {
        return Arrays.asList(
            new Book("Clean Code", "Robert C. Martin", 464),
            new Book("The Pragmatic Programmer", "Andy Hunt", 352)
        );
    }
}

@WebServlet(urlPatterns = "/graphql")
public class GraphQLEndpoint extends SimpleGraphQLHttpServlet {
    // Configure schema and resolver wiring
}

Explanation

GraphQL APIs consist of three core components:

  • Schema: Defines types, queries, mutations, and subscriptions using SDL (Schema Definition Language)
  • Resolvers: Functions that return data for each field in the schema
  • Server: Handles HTTP requests, parses queries, validates against schema, and executes resolvers

Key differences across languages:

  • Python (Strawberry): Decorator-based type definitions with dataclass syntax
  • JavaScript (Apollo): Schema-first with gql template literals
  • Java: Code-first or schema-first with library-specific resolvers

Variants

TechnologyLibraryApproachNotes
PythonStrawberryCode-firstDataclass decorators, FastAPI integration
PythonGrapheneCode-firstDjango integration, mature ecosystem
JavaScriptApollo ServerSchema-firstFederation, subscriptions, caching
JavaScriptNexusCode-firstTypeScript-first, type inference
Javagraphql-javaSchema-firstLow-level, maximum control
JavaDGS FrameworkCode-firstNetflix open-source, Spring integration

Best Practices

  • Use DataLoader for N+1 queries: Batch and cache database requests across resolvers
  • Implement pagination: Use cursor-based pagination for large lists (Relay Connection spec)
  • Validate input early: Use schema directives and custom scalars for input validation
  • Limit query depth/complexity: Prevent expensive queries with depth and complexity analysis
  • Enable query whitelisting in production: Use persisted queries to prevent arbitrary query execution

Common Mistakes

  • Not handling N+1 queries: Each resolver hitting the database independently causes exponential queries
  • Over-exposing internal types: Leaking database models directly into the schema without a domain layer
  • Missing error handling: GraphQL returns 200 OK even with errors — always check the errors array
  • Ignoring schema versioning: While GraphQL avoids versioning, deprecation and field tracking still matter
  • Storing state in resolvers: Resolvers must be stateless; use context for request-scoped data

Frequently Asked Questions

Q: Should I migrate my REST API to GraphQL? A: Not necessarily. GraphQL shines when clients need flexibility. If your API has simple, stable consumers, REST may be simpler and more cacheable.

Q: How do I handle file uploads in GraphQL? A: Use the multipart request spec (Apollo supports it natively) or use a separate REST endpoint for uploads and return the URL in GraphQL.

Q: What is GraphQL federation? A: Federation allows multiple GraphQL services to expose a unified schema. Each service owns part of the schema, and a gateway stitches them together. Ideal for microservices.