GraphQL API with TypeScript: Step-by-Step Tutorial
GraphQL is a query language for APIs that allows clients to request only the data they need. Combined with TypeScript, static typing helps avoid errors at the development stage and makes the code more predictable. In this tutorial, we will create a full-fledged GraphQL API using Apollo Server and TypeScript.
We will cover: project setup, schema and type definition, resolver implementation, mutations for data modification, and database integration (in-memory for simplicity). By the end of the article, you will be able to run your own GraphQL server.
1. Project Preparation and Dependency Installation
First, let's create a new Node.js project and install the necessary packages.
mkdir graphql-typescript-tutorial
cd graphql-typescript-tutorial
npm init -y
npm install apollo-server graphql
npm install -D typescript @types/node ts-node nodemon
Let's create the tsconfig.json file:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
The project structure will be as follows:
src/index.ts— entry pointsrc/schema.ts— GraphQL schemasrc/resolvers.ts— resolverssrc/types.ts— TypeScript types
2. Defining GraphQL Types and Schema
The GraphQL schema describes what data is available and how to request it. For this example, we'll create a simple user and post management system.
Let's create src/types.ts:
export interface User {
id: number;
name: string;
email: string;
}
export interface Post {
id: number;
title: string;
content: string;
authorId: number;
}
export interface CreateUserInput {
name: string;
email: string;
}
export interface CreatePostInput {
title: string;
content: string;
authorId: number;
}
Now let's create src/schema.ts with the GraphQL type definitions:
import { gql } from 'apollo-server';
export const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
input CreateUserInput {
name: String!
email: String!
}
input CreatePostInput {
title: String!
content: String!
authorId: ID!
}
type Query {
users: [User!]!
user(id: ID!): User
posts: [Post!]!
post(id: ID!): Post
}
type Mutation {
createUser(input: CreateUserInput!): User!
createPost(input: CreatePostInput!): Post!
}
`;
Note: we use input types for mutations — this is a standard practice in GraphQL.
3. Implementing Resolvers with TypeScript
Resolvers are functions that return data for each field of the schema. Let's create src/resolvers.ts:
import { User, Post, CreateUserInput, CreatePostInput } from './types';
// In-memory database
let users: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
];
let posts: Post[] = [
{ id: 1, title: 'GraphQL Basics', content: '...', authorId: 1 },
{ id: 2, title: 'TypeScript Guide', content: '...', authorId: 2 },
];
let nextUserId = 3;
let nextPostId = 3;
export const resolvers = {
Query: {
users: (): User[] => users,
user: (_: unknown, args: { id: string }): User | undefined =>
users.find(u => u.id === Number(args.id)),
posts: (): Post[] => posts,
post: (_: unknown,
```