Express.js is the most popular Node.js framework for building APIs, and for good reason: minimal, flexible, and backed by an enormous ecosystem. But going from a working Express app to a production-ready API with authentication, rate limiting, and developer documentation is a bigger jump than most tutorials acknowledge.
In this tutorial, you’ll build a REST API with Express.js, generate an OpenAPI spec, and deploy it behind Zuplo as an API gateway. By the end, you’ll have rate limiting, API key authentication, and an auto-generated developer portal, without writing any middleware for those concerns.
- Building a REST API with Node.js and Express
- Looking for a straightforward deployment workflow
- Need to add authentication and rate limiting without writing custom middleware
- Want auto-generated API documentation from your OpenAPI spec
Prerequisites
Build a CRUD API with Express.js
Start by creating a new project directory and initializing it:
Create an index.js file with a basic CRUD API for managing books:
Run it locally to verify everything works:
Test it with curl:
You should see your book returned as JSON. That’s the foundation: a clean CRUD
API in about 50 lines of code. The in-memory books array resets on every
restart, which is fine for the tutorial but not something you’d ship. Swap it
for a real database when you’re ready.
Add an OpenAPI specification
An OpenAPI spec is essential for integrating with Zuplo (and for any
production-quality API). You can write one by hand, but swagger-jsdoc
generates it from JSDoc comments in your code.
Install the dependencies:
Update index.js to add Swagger configuration and JSDoc annotations. Add this
near the top of the file, after the express require:
apis: ["./index.js"] tells swagger-jsdoc where to look for @openapi
comments. Keep all your JSDoc in index.js (or expand the glob if you split the
routes across files), otherwise the generated spec will be empty.
Then add a route to serve the spec (before app.listen):
This is the URL you’ll hand to Zuplo when you import the spec. You don’t need to serve the docs UI yourself, since Zuplo’s developer portal will render them from the same spec at the end of this tutorial.
Now annotate your endpoints. Add JSDoc comments above each route handler. Here are the list and create endpoints fully annotated:
Add the schema definitions anywhere in the file. swagger-jsdoc scans every
@openapi comment and merges them into a single document, so placement doesn’t
matter, but keeping shared schemas near the top makes the file easier to scan:
Follow the same pattern for GET /books/:id, PUT /books/:id, and
DELETE /books/:id. The :id path parameter needs a parameters block:
Restart the server and visit http://localhost:3000/openapi.json to see the
generated spec.
Deploy your Express app
Your API needs a public URL so Zuplo can proxy to it. Any Node.js host works (Railway, Render, Fly.io, or a VPS). We’ll use Railway because it’s quick.
First, add a start script to your package.json:
Push your code to a GitHub repository, then connect it to Railway:
- Go to railway.app and create a new project
- Select Deploy from GitHub repo and pick your repository
- Railway detects Node.js automatically and deploys
Once deployed, you’ll get a public URL like
https://express-bookstore-api-production.up.railway.app. Verify it works by
hitting the /books endpoint in your browser.
Save that URL. You’ll need it when configuring Zuplo.
Set up Zuplo as your API gateway
Now for the part that turns your Express API into a production-ready service. Zuplo sits in front of Express as an API gateway, handling rate limiting, authentication, and documentation so you don’t have to build them yourself.

Each request from a client hits Zuplo first, flows through the policy pipeline (API key auth, then rate limiting), and only then gets forwarded to your Express app on Railway.
Create a Zuplo project
- Sign in at portal.zuplo.com and create a new project
- Select an empty project and give it a name (e.g.,
bookstore-api) - Click Start Building
Import your OpenAPI spec
Instead of creating routes manually, import the OpenAPI spec from your Express app:
- In the Zuplo portal, open the Code tab and click routes.oas.json. This is Zuplo’s own config file. It’s an OpenAPI document with extra fields per route for policies and request handlers, and it’s separate from the spec your Express app serves.
- Click Import OpenAPI
- Download your spec from
https://your-railway-url.up.railway.app/openapi.jsonand upload it
Your endpoints are merged into routes.oas.json, which is what you’ll edit from
here on. You should now see all five routes in the route designer.

Configure the URL Forward Handler
Each imported route needs to know where to send requests. Zuplo’s URL Forward Handler proxies to your Express backend by appending the request path to a base URL.
- Go to Settings > Environment Variables
- Add a variable called
BASE_URLwith the value set to your Railway URL (e.g.,https://express-bookstore-api-production.up.railway.app) - Back in the route designer, set each route’s Request Handler to URL
Forward with the Forward to value set to
${env.BASE_URL} - Save your changes
Test it by clicking the Test button on any route. You’ll see the same responses as hitting Express directly, but now the request flows through Zuplo. The URL you give to consumers is your Zuplo gateway URL, not the Railway URL.
Common mistake:
Your Railway URL is still public, so a caller who knows it can skip the gateway entirely. Lock the origin down so it only accepts traffic from Zuplo, either by checking a shared secret header in your Express app or by using your host’s IP allowlist.
URL Forward Handler
The URL Forward Handler proxies requests to your backend without writing any code. It appends the incoming path to your configured base URL and forwards query parameters automatically.
Add rate limiting
Without rate limiting, a single client could overwhelm your Express server. Zuplo’s Rate Limiting policy adds protection with zero code changes.
- Open any route in the route designer
- Click + Add Policy on the request pipeline
- Search for Rate Limiting and select it
- Adjust the values. Start with something tight so it’s easy to trigger while
you test. These are the
optionson the policy:
- Click OK and save
Test it by sending requests rapidly. After exceeding the limit, you’ll get a
429 Too Many Requests response with a retry-after header.
Apply the policy to every route for consistent protection. You can set different limits per route if some endpoints are more expensive than others.
Pro tip:
Pipeline order matters. When you add the API key policy in the next section, put it above the rate limiter so the limiter can bucket by authenticated consumer instead of IP.
Rate Limiting Policy
Zuplo runs the rate limiter on a globally distributed gateway in front of your origin. You can limit by IP, user, API key, or custom function, all without touching your Express code.
Add API key authentication
Rate limiting by IP is a start, but for a real API you want to identify consumers individually. Zuplo’s API Key Authentication policy gives you a managed key system with no auth middleware.
Enable the policy
- Open a route and click + Add Policy
- Search for API Key Authentication and add it
- Accept the default configuration and click OK
- Important: drag the API key policy above the rate limiting policy. Authentication runs first so the rate limiter can see the authenticated consumer, which you’ll switch to in the next step
Save. Testing the route now gives you 401 Unauthorized, exactly what you want.

Create an API key consumer
- Click the Services tab at the top of the Zuplo portal
- Select Configure on the API Key Service
- Click Create Consumer
- Enter a name (e.g.,
test-user) and an email address - Save the consumer
Zuplo generates an API key for you. Copy it.
Test authenticated requests
In the route tester, add an Authorization header with the value
Bearer YOUR_API_KEY and send the request. You should get a 200. The header
name and scheme are configurable if your consumers expect X-API-Key instead.
Without the header (or with an invalid key), you’ll still get 401. Your API is
secured.
Now update the rate limiting policy to use "rateLimitBy": "user" instead of
"ip". With auth running first, Zuplo attaches the authenticated consumer to
the request, and "user" tells the rate limiter to bucket by that consumer.
Limits now follow the API key, which is what you want for an API with multiple
consumers.
API Key Authentication Policy
Zuplo's built-in API key management handles creation, rotation, and revocation. Consumers can self-serve through the developer portal.
Launch your developer portal
Every API needs documentation, and Zuplo generates it from the OpenAPI spec you imported.
Click Gateway deployed in the portal toolbar and open the Developer Portal link. You’ll see all your routes rendered as interactive docs, complete with the descriptions, request/response schemas, and auth requirements from your OpenAPI spec.

The portal also includes:
- An API playground to test endpoints directly
- API key management for authenticated users to manage their keys
- Automatic auth documentation reflecting the policies you’ve applied
No extra configuration needed. If your OpenAPI spec has good summaries and descriptions, the portal looks professional out of the box.
Developer Portal
Zuplo automatically generates an interactive developer portal from your OpenAPI spec. It includes API key management, a request playground, and full endpoint documentation.
The complete picture
Here’s what you’ve built:
- Express.js backend, a CRUD API with OpenAPI annotations
- Public deployment on Railway (or your preferred host)
- Zuplo gateway in front of Express, handling cross-cutting concerns
- Rate limiting, enforced by the gateway
- API key authentication with managed keys, no custom middleware
- Developer portal, auto-generated docs with a playground and self-serve key management
The Express app stays focused on business logic. Every operational concern (auth, rate limiting, documentation) lives at the gateway layer.
That separation is the point of an API gateway. Your Express code doesn’t need to know about rate limits or API keys. You can swap policies independently and you don’t have to redeploy Express every time you change an auth strategy or adjust a limit.
Next steps
A few directions from here:
- Add request validation with Zuplo’s Request Validation policy to enforce body schemas at the gateway
- Set up per-consumer rate limits with dynamic rate limiting based on API key metadata
- Monetize your API with Zuplo’s monetization features to attach billing plans to keys
- Develop locally with the Zuplo CLI for a git-based workflow
Whatever you’re building with Express.js, Zuplo handles the gateway layer so you can focus on the code that matters.