In this lesson, we'll focus on securing your GraphQL API by implementing rate limiting. As we secure our APIs, it's crucial to prevent abuse, and rate limiting is a powerful tool for this purpose. Rate limiting helps manage the number of requests a user can make to your API within a specific time frame, ensuring it can handle heavy loads gracefully.
We'll use the express-rate-limit
library in this lesson. This library is popular for rate-limiting in Express applications due to its simplicity and flexibility.
By the end of this lesson, you'll be equipped to add rate limiting to your GraphQL API, protecting your resources and improving the performance and reliability of your server.
The schema defines the structure of the data and the queries that can be made. We'll define a simple schema for our books example:
TypeScript1import { gql } from 'apollo-server-express'; 2 3const typeDefs = gql` 4 type Book { 5 id: ID! 6 title: String! 7 author: String! 8 } 9 10 type Query { 11 books: [Book] 12 } 13`;
Here's a brief explanation:
type Book
: Defines a Book type with fieldsid
,title
, andauthor
, all of which are non-nullable.type Query
: Defines a query to fetch a list of books.
Now, let's introduce rate limiting to our Express application. Middleware in Express is a function that handles requests and responses. We'll use the express-rate-limit
library for this. Here's how to set it up:
First, import the necessary libraries:
TypeScript1import express from 'express'; 2import rateLimit from 'express-rate-limit';
Next, define the rate-limiting settings:
TypeScript1const app = express(); 2 3const limiter = rateLimit({ 4 windowMs: 15 * 60 * 1000, // 15 minutes 5 max: 100, // limit each IP to 100 requests per window 6 handler: (_, res) => { 7 res.status(429).send('Too many requests'); 8 } 9}); 10 11app.use(limiter);
Here's a breakdown of this code:
windowMs
: The time frame for the rate limit (15 minutes in this case).max
: The maximum number of requests a user can make within the window (100 requests).handler
: Custom response when the rate limit is exceeded (429
status code).
To use different rate limiter for different API endpoints, you can use app.use('/endpoint', limiter)
notation.
Next, we need to set up Apollo Server with Express. We'll start the Apollo Server and merge it with our existing Express app. Here's how:
First, import the necessary libraries:
TypeScript1import { ApolloServer } from 'apollo-server-express';
Then, define sample data and resolvers:
TypeScript1const books = [ 2 { id: '1', title: 'The Hobbit', author: 'J.R.R. Tolkien' }, 3 { id: '2', title: 'Harry Potter', author: 'J.K. Rowling' } 4]; 5 6const resolvers = { 7 Query: { 8 books: () => books, 9 }, 10};
Finally, initialize and start the Apollo Server:
TypeScript1const server = new ApolloServer({ typeDefs, resolvers }); 2 3server.start().then(() => { 4 server.applyMiddleware({ app, path: '/graphql' }); 5 6 app.listen({ port: 4000 }, () => { 7 console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`); 8 }); 9});
In this setup:
typeDefs
: Our schema definition.resolvers
: Functions to handle queries.applyMiddleware
: Integrates Apollo Server with Express under the/graphql
path.app.listen
: Starts the server on port 4000 and logs the server URL.
To test our implementation, we'll query the GraphQL API to see if rate limiting is working as intended. Here's a Node.js script using node-fetch
to send a query:
TypeScript1import fetch from 'node-fetch'; 2 3const query = ` 4 query { 5 books { 6 id 7 title 8 author 9 } 10 } 11`; 12 13const url = 'http://localhost:4000/graphql'; 14 15(async () => { 16 for (let i = 0; i < 5; i++) { 17 try { 18 const response = await fetch(url, { 19 method: 'POST', 20 headers: { 21 'Content-Type': 'application/json', 22 }, 23 body: JSON.stringify({ query }), 24 }); 25 26 if (response.ok) { 27 const data = await response.json(); 28 console.log(JSON.stringify(data, null, 2)); 29 } else if (response.status === 429) { 30 const text = await response.text(); 31 console.error(`Error: ${text}`); 32 } 33 34 } catch (error) { 35 console.error('Error:', error); 36 } 37 } 38})();
Running this script should provide a response with the list of books from our server:
JSON1{ 2 "data": { 3 "books": [ 4 { "id": "1", "title": "The Hobbit", "author": "J.R.R. Tolkien" }, 5 { "id": "2", "title": "Harry Potter", "author": "J.K. Rowling" } 6 ] 7 } 8}
If you exceed the rate limit by running this script more than 100 times in 15 minutes, you should see a 429 Too Many Requests
response.
In this lesson, we covered:
- Defining a simple GraphQL schema.
- Applying rate limiting middleware to an Express app.
- Integrating Apollo Server with Express to secure GraphQL APIs.
- Testing the implementation to observe rate limiting in action.
By applying these techniques, you ensure your GraphQL API is more secure and can handle high loads in a stable manner. Well done on reaching the end of this course! Now, it's time to put your knowledge into practice with the exercises that follow. Keep exploring and refining your skills in GraphQL and API security.