You can get a lot done in 2 minutes, like microwaving popcorn, sending a text message, eating a cupcake, and hooking up a GraphQL server. Yup. If you have an old Express.js RESTful API lying around or you‘re interested in incrementally adopting GraphQL, we only need 2 minutes to hook it up with a fresh new GraphQL Server.
Ready? Set. Go!
Why Add GraphQL?
But first – why bother adding GraphQL in the first place? What benefits does it provide over a traditional REST architecture?
Flexibility: GraphQL lets the client dictate exactly what data it needs via queries rather than being restricted to the fixed endpoints provided by REST. This allows for efficient, targeted requests that return only the required fields.
Versioning: As your API evolves, you don‘t need to keep updating endpoint URLs or dealing with breaking changes between versions. The GraphQL query language is the stable layer that remains constant.
Speed: GraphQL queries require only a single round trip to fetch the needed data. No more over and under-fetching resources. This results in improved performance, especially on mobile networks.
Type safety: The GraphQL type system allows complete control and confidence over what data is entered and returned by the API. This acts as built-in validation.
Overall, GraphQL solves many pain points developers face when building and consuming traditional REST APIs. It‘s no wonder that companies like GitHub, Yelp, and Credit Karma have publicly talked about the successes they‘ve achieved after adopting GraphQL.
Now let‘s look at how easy Apollo Server makes it to add a GraphQL layer to your Express app…
Existing App Setup
Let‘s say your Express server looked something like this originally:
import express from ‘express‘;import { apiRouter } from ‘./router‘; const app = express();const port = process.env.PORT || 5000;// Existing routes for our Express.js app app.use(‘/api/v1‘, apiRouter);app.listen(port, () => console.log(`[App]: Listening on port ${port}`))
This exposes a set of REST endpoints from apiRouter
under the /api/v1
path that our frontends can consume.
Now we want to augment this API with the ability to also handle GraphQL requests.
Install Apollo Server
The first step is to install the Apollo Server Express integration package:
npm install apollo-server-express
This contains everything we need to wire up Apollo with our Express app.
Import Apollo Server
At the top of the file, let‘s import ApolloServer
and gql
from apollo-server-express
:
import { ApolloServer, gql } from ‘apollo-server-express‘;
ApolloServer
is the primary class we use to configure our GraphQL server.
gql
is a parser function that lets us define our type definitions using GraphQL schema language (SDL) inside JavaScript template literals.
Define Schema
Now we can define a simple schema for our API:
const typeDefs = gql` type Query { hello: String }`;
This schema has one query field called hello
that returns a String
.
Let‘s also specify the corresponding resolver function that populates this field:
const resolvers = { Query: { hello: () => ‘Hello world!‘, },};
Resolvers map schema fields to resolve functions that fetch the requested data.
Create Apollo Server Instance
With the schema and resolvers defined, we can now create an instance of ApolloServer
:
const server = new ApolloServer({ typeDefs, resolvers,});
We pass our typeDefs
and resolvers
into the constructor to register them with the server.
Apply Middleware
The last step is to apply Apollo Server‘s middleware into the Express application pipeline:
server.applyMiddleware({ app });
This connects Apollo handling GraphQL requests from the provided Express instance.
By default, it will mount the GraphQL endpoint at /graphql
.
We could also provide a custom path:
server.applyMiddleware({ app, path: ‘/api/graphql‘ });
Final Server Code
Bringing this all together, here is what the complete server code looks like now:
import express from ‘express‘;import { apiRouter } from ‘./router‘; import { ApolloServer, gql } from ‘apollo-server-express‘;const app = express();const server = new ApolloServer({ typeDefs, resolvers });server.applyMiddleware({ app }); app.use(‘/api/v1‘, apiRouter); app.listen(5000);
Our existing API routes will continue handling REST traffic, but now any requests to /graphql
will be handled by the Apollo GraphQL server.
Let‘s test it out! 🚀
Using the GraphQL Playground
If we navigate browsers to http://localhost:5000/graphql
, the GraphQL playground should now be accessible:
This interactive UI allows us to easily explore our schema and test queries without needing any client-side code.
Let‘s try running our one and only query:
query { hello }
We should see the "Hello World" result displayed:
And with that, our basic GraphQL server is up and running on top of Express ⚡⚡
There‘s obviously a lot more we can do in terms of defining a production-ready schema, resolvers, and other functionality – but this shows just how quick and simple the initial Apollo Server setup can be.
Recap
To summarize, here is what we did:
- Installed the
apollo-server-express
package - Imported
ApolloServer
andgql
- Defined GraphQL schema and resolvers
- Created an
ApolloServer
instance - Connected the server middleware to Express with
applyMiddleware()
By adding just these few lines of code, we unlocked the ability to handle GraphQL queries in our existing API!
Why This Works
A key reason Apollo Server integrates so seamlessly is its modular architecture:
The core server package remains unopinionated and agnostic to any specific Node.js web framework.
The express integration then handles adapting to that context.
This clean separation of concerns is what allows the Apollo team to release adapters for new frameworks over time without disrupting existing functionality.
Securing Your Endpoint
Our GraphQL endpoint is now exposed publically by default when mounting Apollo, which poses a security risk.
Let‘s discuss a couple quick ways to lock this endpoint down.
Whitelisting
One simple option is to set up some middleware that checks the request IP against a whitelist:
const whitelist = [‘127.0.0.1‘] app.use(‘/graphql‘, (req, res, next) => { const ip = req.ip; if (whitelist.includes(ip)) { next(); } else { res.sendStatus(403); }})
Now only whitelisted IPs will be granted access.
Authentication
For a more robust solution, we can require authentication.
Apollo provides some utilities that integrate nicely with common Node authentication libraries like Passport:
const { ApolloServer } = require(‘apollo-server-express‘);const depthLimit = require(‘graphql-depth-limit‘);const { createComplexityLimitRule } = require(‘graphql-validation-complexity‘);const passport = require(‘passport‘); const jwt = require(‘jsonwebtoken‘);const server = new ApolloServer({ schema, validationRules: [depthLimit(5), createComplexityLimitRule(1000)], context: ({ req }) => ({ user: req.user }), plugins: [ { async serverWillStart() { return { async drainServer() { await server.stop(); } }; } } ]});server.applyMiddleware({ app, path: ‘/graphql‘, cors: false });app.use( ‘/graphql‘, passport.authenticate(‘jwt‘, { session: false }), (req, res, next) => { next(); });
Now only requests with a valid JWT token will reach the Apollo GraphQL server.
There are many other creative ways to implement security besides these examples.
Best Practices
Here are some key best practices to follow as you build out your schema and API functionality further:
Schema Organization
Use a modular approach to split your schema into different domains and compose them together:
import UserSchema from ‘./user‘;import PostSchema from ‘./post‘;const linkSchema = gql` scalar Date type Query { _: Boolean } type Mutation { _: Boolean }`;export default [linkSchema, UserSchema, PostSchema];
Resolvers
Handle resolver logic in dedicated files/classes instead of inline. Maintain separation of concerns.
Pagination
Add pagination capability early using Relay-style connections with cursors.
Cached Data
Set proper cache hints on fields to enable performance optimizations.
Error Handling
Setup error logging and monitoring with plugins. Return application-specific errors.
Validation
Leverage validator directives for things like authentication, authorization, abuse detection etc.
Following these patterns from the start will pay dividends as your schema complexity increases over time.
Common Issues
Here are some common pitfalls to avoid:
- Allowing excessive nesting depth leading to overflow errors
- Enabling expensive operations like CSV export without throttling
- Accidentally exposing underlying database schema
- Failing to anticipate cost of complex queries
- Not versioning the API as changes are introduced
Set query depth limits, restrict mutation access, deny introspection in production, and implement service layer abstraction to safeguard against these.
Where To Go From Here
Now that you‘ve added an initial GraphQL server, here are some suggested next steps:
- Work through the Apollo fullstack tutorial to build an example app
- Watch Apollo‘s GraphQL tutorials
- Read the Production Checklist for launch prep
- Understand how to optimize your schema
- Check out Apollo client libraries like React Apollo
Here are some more great tutorials to explore:
- Authentication with Express + GraphQL
- File uploads in GraphQL with Express
- GraphQL Directives tutorial
- Self-documenting GraphQL APIs
The Apollo community is full of experienced GraphQL developers always willing to help guide newcomers.
So don‘t hesitate to get involved on Apollo Discord or our forum!
I‘m also available over on Twitter if you have any feedback or questions on this article.
Happy coding! 🚀