GraphQL is a query language and runtime for APIs that was developed by Facebook. Unlike traditional REST APIs, which expose a fixed set of endpoints and return predefined data structures, GraphQL APIs allow clients to specify exactly what data they need and get back only that data in a predictable format. This makes it easier to build efficient, flexible APIs that can evolve over time.
In this tutorial, we’ll walk through how to set up a Node.js server with GraphQL and TypeScript. We’ll cover the basics of GraphQL, including queries, mutations, resolvers, and nested resolvers.
Prerequisites
Before we get started, make sure you have the following installed on your system:
- Node.js (v14 or later)
- npm or yarn package manager
Step 1: Initialize a new Node.js project
To get started, let’s create a new Node.js project. Open up a terminal and run the following commands:
mkdir my-graphql-project
cd my-graphql-project
npm init -y
This will create a new directory called my-graphql-project
and initialize a new Node.js project inside it.
Step 2: Install dependencies
Next, we’ll install the dependencies we need for our project. We’ll use the following packages:
apollo-server-express
: A package for creating GraphQL servers with Express.jsgraphql
: The main GraphQL librarytypescript
: A superset of JavaScript that adds type checking and other features
Run the following command to install these packages:
npm install apollo-server-express graphql typescript
Step 3: Set up TypeScript
Now that we have TypeScript installed, let’s set up our project to use it. Create a new file called tsconfig.json
in the root of your project with the following contents:
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"lib": ["es2017"],
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"sourceMap": true,
"declaration": true,
"declarationDir": "dist/types"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
This sets up the TypeScript compiler with some basic options, including the outDir
option, which specifies the output directory for compiled TypeScript files, and the rootDir
option, which specifies the root directory for TypeScript source files.
Step 4: Create a GraphQL schema
Now that our project is set up with TypeScript, let’s create a GraphQL schema. Create a new file called schema.graphql
in the src
directory with the following contents:
type Query {
hello: String
}
type Mutation {
addUser(input: AddUserInput!): User!
}
type User {
id: ID!
name: String!
email: String!
role: String!
}
input AddUserInput {
name: String!
email: String!
role: String!
}
This defines a simple GraphQL schema with a Query
type, a Mutation
type, and a User
type. The Query
type has a single field called hello
that returns a string. The Mutation
type has a single field called addUser
that takes an AddUserInput
object as an argument and returns a User
object. The User
type has four fields: id
, name
, email
, and role
.
Step 5: Define resolvers
Next, we need to define resolvers for our schema. Resolvers are functions that tell GraphQL how to fetch data for a particular field. Create a new file called resolvers.ts
in the src
directory with the following contents:
import { AddUserInput, User } from './types';
const users: User[] = [
{ id: '1', name: 'John Doe', email: 'john.doe@example.com', role: 'admin' },
{ id: '2', name: 'Jane Doe', email: 'jane.doe@example.com', role: 'user' },
];
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
Mutation: {
addUser: (_parent, { input }: { input: AddUserInput }): User => {
const id = String(users.length + 1);
const user = { id, ...input };
users.push(user);
return user;
},
},
};
export default resolvers;
In this file, we define a users
array that contains some sample user data. We then define a resolvers
object with two fields: Query
and Mutation
. The hello
resolver for the Query
type simply returns a string.
The addUser
resolver for the Mutation
type takes an input
argument of type AddUserInput
and adds a new user to the users
array with a unique ID. The resolver then returns the new user object.
Step 6: Set up the server
Now that we have our schema and resolvers defined, let’s set up the server. Create a new file called server.ts
in the src
directory with the following contents:
import express from 'express';
import { ApolloServer, gql } from 'apollo-server-express';
import resolvers from './resolvers';
const typeDefs = gql`
${require('fs').readFileSync(require.resolve('./schema.graphql'), 'utf8')}
`;
const server = new ApolloServer({ typeDefs, resolvers });
const app = express();
server.applyMiddleware({ app });
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server listening on http://localhost:${PORT}${server.graphqlPath}`);
});
In this file, we first import the necessary packages, including express
, ApolloServer
, and our resolvers
object.
We then read the schema.graphql
file using the readFileSync
method from the fs
module and pass the resulting string to the gql
function to create a GraphQL schema object.
We create a new instance of ApolloServer
with our typeDefs
and resolvers
, and then set up an Express app and apply the Apollo middleware to it.
Finally, we start the server by calling the listen
method on the app object and logging a message to the console.
Before you move to the next step, ensure that your package.json file has required script. With the above steps, it would look like this
{
"name": "my-graphql-project",
"version": "1.0.0",
"description": "A simple GraphQL API with TypeScript",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/server.ts",
"start": "node dist/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Luc",
"license": "MIT",
"dependencies": {
"apollo-server-express": "^3.7.0",
"express": "^4.17.1",
"graphql": "^16.3.0"
},
"devDependencies": {
"@types/express": "^4.17.13",
"@types/graphql": "^15.0.5",
"@types/node": "^16.11.1",
"nodemon": "^2.0.15",
"ts-node": "^10.4.0",
"typescript": "^4.4.4"
}
}
Step 7: Start the server
We’re now ready to start the server. Run the following command to start the server:
npm run dev
This will start the server in development mode using Nodemon, which will automatically restart the server whenever you make changes to your code.
In this tutorial, we walked through how to set up a Node.js server with GraphQL and TypeScript. We covered the basics of GraphQL, including queries, mutations, resolvers, and nested resolvers. We also discussed the benefits of using GraphQL over REST, such as increased flexibility and efficiency.
With this setup, you can now start building a GraphQL API for your project. You can add new types to the schema as needed and define resolvers to fetch data from your database or other data sources.
Keep in mind that this is just a basic setup to get you started. Depending on the complexity of your project, you may need to add additional packages or configurations to handle things like authentication, caching, and error handling.
Overall, however, Node.js, GraphQL, and TypeScript provide a powerful combination for building modern, scalable APIs that can easily evolve over time.