GraphQL Tutorial: Build Your First API Step by Step
Tutorials

GraphQL Tutorial: Build Your First API Step by Step

14 min read
96 Views
Share:

Why GraphQL matters in 2026

After building REST APIs for years, I switched to GraphQL for a complex project and never looked back for certain use cases. GraphQL solves two fundamental problems with REST: over-fetching (getting more data than you need) and under-fetching (needing multiple requests to get related data).

Created by Facebook in 2015, GraphQL is a query language for APIs that lets clients request exactly the data they need in a single request. In 2026, it powers APIs at GitHub, Shopify, Stripe, and thousands of other companies.

GraphQL vs REST: when to use which

FeatureRESTGraphQL
Data fetchingFixed endpoints, fixed data shapeClient specifies exact fields needed
Multiple resourcesMultiple requests neededSingle request for related data
VersioningURL versioning (/v1/, /v2/)Schema evolution, no versioning needed
CachingHTTP caching built-inRequires client-side caching (Apollo)
Learning curveLowMedium
Best forSimple CRUD, microservicesComplex data relationships, mobile apps

Setting up your first GraphQL server

# Create project
mkdir graphql-api && cd graphql-api
npm init -y

# Install dependencies
npm install @apollo/server graphql

# For TypeScript (recommended)
npm install -D typescript @types/node ts-node
npx tsc --init

Define your schema

The schema is the contract between your client and server. It defines what data is available and what operations clients can perform.

// src/schema.ts
export const typeDefs = `#graphql
  type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
    createdAt: String!
  }

  type Post {
    id: ID!
    title: String!
    content: String!
    published: Boolean!
    author: User!
    createdAt: String!
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
    posts(published: Boolean): [Post!]!
    post(id: ID!): Post
  }

  type Mutation {
    createUser(name: String!, email: String!): User!
    createPost(title: String!, content: String!, authorId: ID!): Post!
    publishPost(id: ID!): Post!
    deletePost(id: ID!): Boolean!
  }
`;

Implement resolvers

// src/resolvers.ts
const users = [
  { id: "1", name: "Alice", email: "alice@example.com", createdAt: new Date().toISOString() },
  { id: "2", name: "Bob", email: "bob@example.com", createdAt: new Date().toISOString() },
];

const posts = [
  { id: "1", title: "GraphQL Basics", content: "Learn GraphQL...", published: true, authorId: "1", createdAt: new Date().toISOString() },
  { id: "2", title: "Advanced Queries", content: "Deep dive...", published: false, authorId: "1", createdAt: new Date().toISOString() },
];

let nextUserId = 3;
let nextPostId = 3;

export const resolvers = {
  Query: {
    users: () => users,
    user: (_: any, { id }: { id: string }) => users.find(u => u.id === id),
    posts: (_: any, { published }: { published?: boolean }) => {
      if (published !== undefined) return posts.filter(p => p.published === published);
      return posts;
    },
    post: (_: any, { id }: { id: string }) => posts.find(p => p.id === id),
  },

  Mutation: {
    createUser: (_: any, { name, email }: { name: string; email: string }) => {
      const user = { id: String(nextUserId++), name, email, createdAt: new Date().toISOString() };
      users.push(user);
      return user;
    },
    createPost: (_: any, { title, content, authorId }: any) => {
      const post = { id: String(nextPostId++), title, content, published: false, authorId, createdAt: new Date().toISOString() };
      posts.push(post);
      return post;
    },
    publishPost: (_: any, { id }: { id: string }) => {
      const post = posts.find(p => p.id === id);
      if (!post) throw new Error("Post not found");
      post.published = true;
      return post;
    },
    deletePost: (_: any, { id }: { id: string }) => {
      const index = posts.findIndex(p => p.id === id);
      if (index === -1) return false;
      posts.splice(index, 1);
      return true;
    },
  },

  // Field resolvers for relationships
  User: {
    posts: (parent: any) => posts.filter(p => p.authorId === parent.id),
  },
  Post: {
    author: (parent: any) => users.find(u => u.id === parent.authorId),
  },
};

Start the server

// src/index.ts
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { typeDefs } from "./schema";
import { resolvers } from "./resolvers";

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

const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
console.log(`GraphQL server ready at ${url}`);

Querying your API

# Get all users with their posts (single request!)
query {
  users {
    name
    email
    posts {
      title
      published
    }
  }
}

# Get a specific user
query {
  user(id: "1") {
    name
    posts {
      title
      content
    }
  }
}

# Create a new post
mutation {
  createPost(title: "My New Post", content: "Hello GraphQL!", authorId: "1") {
    id
    title
    author {
      name
    }
  }
}

Troubleshooting common issues

Issue 1: "Cannot return null for non-nullable field". Your resolver is returning null or undefined for a field marked with ! (non-nullable). Check that your data source actually contains the expected data and your resolver logic is correct.

Issue 2: N+1 query problem. When fetching a list of users with their posts, the posts resolver runs once per user, causing N+1 database queries. Solution: use DataLoader to batch and cache database queries.

Issue 3: Schema validation errors. If your schema does not compile, check for typos in type names, missing exclamation marks, and ensure all referenced types are defined. The Apollo Server error messages are usually descriptive enough to pinpoint the issue.

Additional resources

J
Written by
Jesús García

Apasionado por la tecnologia y las finanzas personales. Escribo sobre innovacion, inteligencia artificial, inversiones y estrategias para mejorar tu economia. Mi objetivo es hacer que temas complejos sean accesibles para todos.

Share post:

Related posts

Comments

Leave a comment

Recommended Tools

The ones we use in our projects

Affiliate links. No extra cost to you.

Need technology services?

We offer comprehensive web development, mobile apps, consulting, and more.

Web Development Mobile Apps Consulting