Introduction to GraphQL and TypeScript
GraphQL is a query language for your API that allows clients to request exactly the data they need, and nothing more. It is often used as an alternative to REST APIs, which require clients to request fixed sets of data and may return unnecessary data.
TypeScript is a superset of JavaScript that adds static typing to the language. This allows developers to catch type errors before runtime and can also provide better code completion and documentation in IDEs.
In this tutorial, we will be using Node.js as the runtime for our GraphQL server. Node.js is a popular runtime for building back-end applications with JavaScript.
Setting up a GraphQL Server with TypeScript
To get started, we will need to install the required dependencies. We will be using the express
and apollo-server-express
packages to set up our GraphQL server. We will also need the @types/express
and @types/graphql
packages to provide TypeScript definitions for these libraries.
To install these dependencies, run the following command:
npm install express apollo-server-express @types/express @types/graphql
Next, let's create a basic GraphQL server using express and apollo-server-express. Create a file called index.ts
and add the following code:
import express from 'express';
import { ApolloServer, gql } from 'apollo-server-express';
const app = express();
const typeDefs = gql type Query { hello: String };
const resolvers = {
Query: {
hello: () => 'Hello, world!',
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.applyMiddleware({ app });
app.listen(4000, () => {
console.log('Server listening on port 4000');
});
This code sets up an express server and creates a simple GraphQL API with a single hello
the field that returns the string "Hello, world!".
To start the server, run the following command:
npm run start
You should see the message "Server listening on port 4000" in the console.
To test the server, open a GraphQL playground by visiting http://localhost:4000/graphql
your browser. You should see a screen like this:
In the left panel, enter the following query:
{
hello
}
Then press the "Play" button. You should see the following response in the right panel:
{
"data": {
"hello": "Hello, world!"
}
}
Congratulations, you have successfully set up a GraphQL server with TypeScript!
Adding a Database
Now that we have a basic GraphQL server up and running, let's add a database to it. We will use a popular NoSQL database called MongoDB and the mongoose
library to interact with it.
To install these dependencies, run the following command:
npm install mongoose
To use MongoDB with our GraphQL server, we will need to create a User
model that defines the shape of a user in the database. Create a new file called models/User.ts
and add the following code:
import mongoose from 'mongoose';
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
});
const User = mongoose.model('User', UserSchema);
export default User;
This code defines a User
model with a username
and password
field.
Next, we will need to connect to our MongoDB database and create a createUser
mutation that allows us to add a new user to the database. Update your index.ts
file with the following code:
import express from 'express';
import { ApolloServer, gql } from 'apollo-server-express';
import mongoose from 'mongoose';
import User from './models/User';
const app = express();
const typeDefs = gql`
type User {
username: String!
password: String!
}
type Query {
hello: String
}
type Mutation {
createUser(username: String!, password: String!): User
}
`;
const resolvers = {
Query: {
hello: () => 'Hello, world!',
},
Mutation: {
createUser: async (_, { username, password }) => {
const user = new User({ username, password });
await user.save();
return user;
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.applyMiddleware({ app });
mongoose.connect(
'mongodb://localhost:27017/test',
{ useNewUrlParser: true }
);
app.listen(4000, () => {
console.log('Server listening on port 4000');
});
This code connects to the test
database on the default MongoDB port (27017) and defines a createUser
mutation that takes a username
and password
as arguments and saves a new user to the database.
To test the createUser
mutation, go back to the GraphQL playground and enter the following mutation:
mutation {
createUser(username: "test", password: "password") {
username
password
}
}
Press the "Play" button and you should see the following response:
{
"data": {
"createUser": {
"username": "test",
"password": "password"
}
}
}
If you check your MongoDB database, you should see a new user with the username "test" and password "password".
Adding Authentication
Now that we have a way to create users, let's add authentication to our GraphQL server. We will use JSON web tokens (JWTs) to authenticate users.
To use JWTs for authentication, we will need to install the jsonwebtoken
package and the @types/jsonwebtoken
package for TypeScript definitions. Run the following command to install these dependencies:
npm install jsonwebtoken @types/jsonwebtoken
Next, let's update our User
model to include a generateAuthToken
method that creates a JWT for a user. Add the following code to your models/User.ts
file:
import mongoose from 'mongoose';
import jwt from 'jsonwebtoken';
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
});
UserSchema.methods.generateAuthToken = function () {
const token = jwt.sign({ _id: this._id }, 'secretkey');
return token;
};
const User = mongoose.model('User', UserSchema);
export default User;
This code defines a generateAuthToken
method on the User
model that creates a JWT with the user's _id
as the payload and a secret key as the signature.
Next, let's create a login
mutation that allows a user to log in with their username and password. Update your index.ts
file with the following code:
import express from 'express';
import { ApolloServer, gql } from 'apollo-server-express';
import mongoose from 'mongoose';
import jwt from 'jsonwebtoken';
import User from './models/User';
const app = express();
const typeDefs = gql`
type User {
username: String!
password: String!
}
type Query {
hello: String
}
type Mutation {
createUser(username: String!, password: String!): User
login(username: String!, password: String!): String
}
`;
const resolvers = {
Query: {
hello: () => 'Hello, world!',
},
Mutation: {
createUser: async (, { username, password }) => {
const user = new User({ username, password });
await user.save();
return user;
},
login: async (, { username, password }) => {
const user = await User.findOne({ username });
if (!user) {
throw new Error('Invalid username or password');
}
if (user.password !== password) {
throw new Error('Invalid username or password');
}
const token = user.generateAuthToken();
return token;
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.applyMiddleware({ app });
mongoose.connect(
'mongodb://localhost:27017/test',
{ useNewUrlParser: true }
);
app.listen(4000, () => {
console.log('Server listening on port 4000');
});
To test the login
mutation, go back to the GraphQL playground, and enter the following mutation:
mutation {
login(username: "test", password: "password")
}
If the login is successful, you should see a JWT in the response:
{
"data": {
"login": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZTgwZTk3MjMzNTY0MTAwMTFhOGJhM2IiLCJpYXQiOjE1OTIyMzM1NjR9.lVnX1Xp-nxo4-L4Z7gEfhJKj1G4Z2_jKm5-m9qqF5xM"
}
}
To verify that the JWT is valid, we can decode it using the jwt.verify
method. Add the following code to your index.ts
file:
jwt.verify(token, 'secretkey', (error, decoded) => {
if (error) {
console.log(error);
} else {
console.log(decoded);
}
});
If you run this code with a valid JWT, you should see the decoded payload in the console.
Now that we can authenticate users, let's add a protected route to our GraphQL server that can only be accessed by authenticated users. Update your index.ts
file with the following code:
import express from 'express';
import { ApolloServer, gql } from 'apollo-server-express';
import mongoose from 'mongoose';
import jwt from 'jsonwebtoken';
import User from './models/User';
const app = express();
const typeDefs = gql`
type User {
username: String!
password: String!
}
type Query {
hello: String
protected: String
}
type Mutation {
createUser(username: String!, password: String!): User
login(username: String!, password: String!): String
}
`;
const resolvers = {
Query: {
hello: () => 'Hello, world!',
protected: (, __, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
return 'This is a protected route';
},
},
Mutation: {
createUser: async (, { username, password }) => {
const user = new User({ username, password });
await user.save();
return user;
},
login: async (_, { username, password }) => {
const user = await User.findOne({ username });
if (!user) {
throw new Error('Invalid username or password');
}
if (user.password !== password) {
throw new Error('Invalid username or password');
}
const token = user.generateAuthToken();
return token
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.applyMiddleware({ app });
mongoose.connect(
'mongodb://localhost:27017/test',
{ useNewUrlParser: true }
);
app.listen(4000, () => {
console.log('Server listening on port 4000');
});
To test the protected route, go back to the GraphQL playground and enter the following query:
query {
protected
}
You should see the following error in the response:
{
"errors": [
{
"message": "Not authenticated",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"protected"
]
}
],
"data": {
"protected": null
}
}
This error is expected because we have not passed an authenticated user to the protected route.
To authenticate the user, we can pass the JWT in the Authorization
header of the request. In the GraphQL playground, click on the "HTTP HEADERS" button and enter the following headers:
{
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZTgwZTk3MjMzNTY0MTAwMTFhOGJhM2IiLCJpYXQiOjE1OTIyMzM1NjR9.lVnX1Xp-nxo4-L4Z7gEfhJKj1G4Z2_jKm5-m9qqF5xM"
}
Make sure to replace the JWT with a valid JWT that you obtained from the login
mutation.
Now, run the protected
query again and you should see the following response:
{
"data": {
"protected": "This is a protected route"
}
}
Congratulations, you have successfully added authentication to your GraphQL server with TypeScript!
Conclusion
In this tutorial, we learned how to set up a GraphQL server with TypeScript and Node.js, how to connect to a MongoDB database, and how to add authentication using JWTs. With these skills, you can build powerful GraphQL APIs with TypeScript and Node.js.