Skip to main content
RapidDev - Software Development Agency
replit-tutorial

How to build microservices with Replit

Replit can host microservice-style APIs using Express (Node.js) or Flask (Python) with multiple route modules, middleware, and Autoscale deployment. Structure your project with separate route files for each service domain, use a central entry point that mounts all routes, add authentication middleware, and deploy with Autoscale for automatic scaling. While Replit runs everything in a single Repl, this modular architecture keeps code organized and maintainable as your API grows.

What you'll learn

  • Structure a multi-endpoint API with modular route files in Replit
  • Add middleware for authentication, logging, and error handling
  • Connect multiple routes to a shared PostgreSQL database
  • Deploy your API with Autoscale for automatic scaling based on traffic
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read20 minutesCore, Pro, and Enterprise plans (Autoscale deployment requires a paid plan)March 2026RapidDev Engineering Team
TL;DR

Replit can host microservice-style APIs using Express (Node.js) or Flask (Python) with multiple route modules, middleware, and Autoscale deployment. Structure your project with separate route files for each service domain, use a central entry point that mounts all routes, add authentication middleware, and deploy with Autoscale for automatic scaling. While Replit runs everything in a single Repl, this modular architecture keeps code organized and maintainable as your API grows.

How to Build Microservices-Style APIs with Replit

As your backend grows, a single monolithic file becomes hard to maintain. This tutorial shows you how to structure a Replit project with multiple API routes organized like microservices: separate modules for users, products, orders, and shared middleware. You will build an Express API with clean route separation, add authentication, and deploy with Autoscale so your API handles production traffic automatically.

Prerequisites

  • A Replit Core or Pro account (needed for Autoscale deployment)
  • Basic knowledge of Express.js or REST API concepts
  • A PostgreSQL database provisioned in Replit (see the database tutorial)
  • API keys stored in Tools → Secrets for any external services

Step-by-step guide

1

Set up the project structure

Create a modular folder structure that separates concerns. Each service domain (users, products, orders) gets its own route file. Shared utilities like database connections and middleware go in their own folders. This structure keeps each file focused on one responsibility and makes it easy to find and modify specific endpoints. Create these folders and files using the file tree or Shell.

typescript
1# Create the project structure in Shell
2mkdir -p src/routes src/middleware src/utils
3touch src/index.js
4touch src/routes/users.js
5touch src/routes/products.js
6touch src/routes/orders.js
7touch src/middleware/auth.js
8touch src/middleware/errorHandler.js
9touch src/utils/db.js

Expected result: Your project has a clean folder structure with separate files for routes, middleware, and utilities.

2

Create the main entry point with route mounting

The main index.js file creates the Express app, applies global middleware, mounts each route module at its path, and starts the server. Use app.use('/api/users', usersRouter) to mount each route file at a base path. This means routes defined in users.js as '/' become '/api/users' in the full URL. Add JSON body parsing, CORS headers, and a health check endpoint.

typescript
1// src/index.js
2const express = require('express');
3const cors = require('cors');
4const { initDatabase } = require('./utils/db');
5const usersRouter = require('./routes/users');
6const productsRouter = require('./routes/products');
7const ordersRouter = require('./routes/orders');
8const errorHandler = require('./middleware/errorHandler');
9
10const app = express();
11const PORT = process.env.PORT || 3000;
12
13// Global middleware
14app.use(cors());
15app.use(express.json());
16
17// Health check
18app.get('/', (req, res) => {
19 res.json({ status: 'healthy', timestamp: new Date().toISOString() });
20});
21
22// Mount route modules
23app.use('/api/users', usersRouter);
24app.use('/api/products', productsRouter);
25app.use('/api/orders', ordersRouter);
26
27// Error handler (must be last)
28app.use(errorHandler);
29
30app.listen(PORT, '0.0.0.0', async () => {
31 await initDatabase();
32 console.log(`API running on 0.0.0.0:${PORT}`);
33});

Expected result: Your Express server starts, initializes the database, and serves all route modules under their respective /api/ paths.

3

Build a route module with CRUD endpoints

Each route file exports an Express Router with endpoints for its domain. Define GET, POST, PUT, and DELETE routes using the router. Keep each route handler focused: validate input, call the database, and return a response. Use async/await with try-catch for clean error handling. Pass errors to the next middleware with next(error) instead of handling them in every route.

typescript
1// src/routes/products.js
2const express = require('express');
3const { query } = require('../utils/db');
4const router = express.Router();
5
6// GET /api/products
7router.get('/', async (req, res, next) => {
8 try {
9 const result = await query(
10 'SELECT * FROM products ORDER BY created_at DESC LIMIT 50'
11 );
12 res.json(result.rows);
13 } catch (err) {
14 next(err);
15 }
16});
17
18// POST /api/products
19router.post('/', async (req, res, next) => {
20 try {
21 const { name, price, description } = req.body;
22 if (!name || !price) {
23 return res.status(400).json({ error: 'name and price are required' });
24 }
25 const result = await query(
26 'INSERT INTO products (name, price, description) VALUES ($1, $2, $3) RETURNING *',
27 [name, price, description]
28 );
29 res.status(201).json(result.rows[0]);
30 } catch (err) {
31 next(err);
32 }
33});
34
35// GET /api/products/:id
36router.get('/:id', async (req, res, next) => {
37 try {
38 const result = await query(
39 'SELECT * FROM products WHERE id = $1',
40 [req.params.id]
41 );
42 if (result.rows.length === 0) {
43 return res.status(404).json({ error: 'Product not found' });
44 }
45 res.json(result.rows[0]);
46 } catch (err) {
47 next(err);
48 }
49});
50
51module.exports = router;

Expected result: Your products route module handles GET, POST, and GET-by-ID requests, returning proper status codes and JSON responses.

4

Add authentication middleware

Create middleware that checks for an API key or JWT token before allowing access to protected routes. Store your API key in Tools → Secrets and check incoming requests against it. Apply the middleware selectively — public routes like the health check do not need authentication, while CRUD operations should require it. For production applications handling real user authentication, RapidDev can help design and implement secure auth flows.

typescript
1// src/middleware/auth.js
2function requireApiKey(req, res, next) {
3 const apiKey = req.headers['x-api-key'];
4 const validKey = process.env.API_KEY;
5
6 if (!validKey) {
7 console.error('API_KEY not configured in Secrets');
8 return res.status(500).json({ error: 'Server configuration error' });
9 }
10
11 if (!apiKey || apiKey !== validKey) {
12 return res.status(401).json({ error: 'Invalid or missing API key' });
13 }
14
15 next();
16}
17
18module.exports = { requireApiKey };
19
20// Usage in index.js:
21// app.use('/api/products', requireApiKey, productsRouter);

Expected result: Protected routes return 401 Unauthorized without a valid API key header, while public routes remain accessible.

5

Add centralized error handling

Create an error handler middleware that catches all errors passed via next(error) from your route handlers. This provides consistent error responses across all endpoints. Log the full error server-side for debugging but return only safe information to the client. The error handler must have four parameters (err, req, res, next) to be recognized by Express as error-handling middleware.

typescript
1// src/middleware/errorHandler.js
2function errorHandler(err, req, res, next) {
3 console.error(`[${new Date().toISOString()}] Error:`, err.message);
4 console.error('Stack:', err.stack);
5
6 // PostgreSQL constraint violations
7 if (err.code === '23505') {
8 return res.status(409).json({ error: 'Resource already exists' });
9 }
10 if (err.code === '23503') {
11 return res.status(400).json({ error: 'Referenced resource does not exist' });
12 }
13
14 // Default server error
15 res.status(err.status || 500).json({
16 error: process.env.NODE_ENV === 'production'
17 ? 'Internal server error'
18 : err.message
19 });
20}
21
22module.exports = errorHandler;

Expected result: All unhandled errors in route handlers are caught by the error handler and return consistent JSON error responses.

6

Deploy with Autoscale

Configure your .replit file with the [deployment] section and publish using Autoscale. Autoscale automatically adjusts the number of instances based on traffic and scales to zero when idle. Make sure your health check endpoint responds in under 5 seconds, your server binds to 0.0.0.0, and all required secrets are in the Deployments pane. Click Publish, select Autoscale, configure CPU and RAM, and deploy.

typescript
1# .replit deployment configuration
2[deployment]
3build = ["npm", "install"]
4run = ["node", "src/index.js"]
5deploymentTarget = "cloudrun"
6
7[[ports]]
8localPort = 3000
9externalPort = 80

Expected result: Your API is deployed and accessible at your .replit.app URL, automatically scaling based on incoming traffic.

Complete working example

src/index.js
1// src/index.js — API gateway for a modular Replit backend
2const express = require('express');
3const cors = require('cors');
4const { initDatabase } = require('./utils/db');
5const { requireApiKey } = require('./middleware/auth');
6const errorHandler = require('./middleware/errorHandler');
7
8const app = express();
9const PORT = process.env.PORT || 3000;
10
11// Global middleware
12app.use(cors());
13app.use(express.json({ limit: '1mb' }));
14
15// Request logging
16app.use((req, res, next) => {
17 const start = Date.now();
18 res.on('finish', () => {
19 const duration = Date.now() - start;
20 console.log(`${req.method} ${req.path} ${res.statusCode} ${duration}ms`);
21 });
22 next();
23});
24
25// Health check (public, no auth)
26app.get('/', (req, res) => {
27 res.json({ status: 'healthy', version: '1.0.0' });
28});
29
30// Protected API routes
31const usersRouter = require('./routes/users');
32const productsRouter = require('./routes/products');
33const ordersRouter = require('./routes/orders');
34
35app.use('/api/users', requireApiKey, usersRouter);
36app.use('/api/products', requireApiKey, productsRouter);
37app.use('/api/orders', requireApiKey, ordersRouter);
38
39// Centralized error handler
40app.use(errorHandler);
41
42// Start server
43app.listen(PORT, '0.0.0.0', async () => {
44 try {
45 await initDatabase();
46 console.log(`API gateway running on 0.0.0.0:${PORT}`);
47 console.log('Routes: /api/users, /api/products, /api/orders');
48 } catch (err) {
49 console.error('Failed to initialize:', err.message);
50 process.exit(1);
51 }
52});

Common mistakes when building microservices with Replit

Why it's a problem: Putting all API routes in a single file that grows to hundreds of lines

How to avoid: Split routes into separate files per domain and mount them with app.use('/api/resource', router)

Why it's a problem: Handling errors individually in every route with duplicate try-catch logic

How to avoid: Use next(error) in routes and create a centralized error handler middleware with four parameters

Why it's a problem: Binding the server to localhost instead of 0.0.0.0, causing 'an open port was not detected' on deploy

How to avoid: Always use app.listen(PORT, '0.0.0.0') in Express to accept external connections

Why it's a problem: Forgetting to add the API_KEY secret in the Deployments pane, breaking auth in production

How to avoid: Check all required secrets exist in both workspace Secrets and Deployments Secrets before publishing

Best practices

  • Organize routes into separate files by domain (users, products, orders) for maintainability
  • Always bind the server to 0.0.0.0 — localhost binding causes deployment health check failures
  • Use a centralized error handler middleware instead of try-catch in every single route
  • Validate request body fields early and return 400 errors before performing database operations
  • Store API keys in Tools → Secrets and add them separately in the Deployments pane for production
  • Add a request logging middleware to track response times and status codes for debugging
  • Use parameterized queries for all database operations to prevent SQL injection
  • Keep the health check endpoint (/) lightweight and public so Autoscale can monitor app status

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have a Replit Express API with users, products, and orders all in one server.js file (400+ lines). How should I restructure it into separate route modules with shared middleware for authentication and error handling?

Replit Prompt

Restructure my Express API into a modular architecture. Create separate route files for /api/users, /api/products, and /api/orders under src/routes/. Add an authentication middleware that checks for an X-API-Key header using a secret from environment variables. Add a centralized error handler. Mount everything in src/index.js.

Frequently asked questions

Each Repl runs as a single process. You can run multiple processes using the run command with & (background operator), but true microservices with separate deployments would require multiple Repls communicating via HTTP. For most projects, a modular monolith within one Repl is simpler and more cost-effective.

Autoscale automatically adds instances when traffic increases and removes them when traffic decreases. It can scale to zero after 15 minutes of inactivity. Cold starts take 10-30 seconds. Configure the maximum machine count in the deployment settings to control costs.

Use curl in the Shell tab to send requests to your local server: curl http://localhost:3000/api/products. You can also use the Preview pane's URL bar to make GET requests, or install a REST client extension.

Yes. Install apollo-server-express or graphql-yoga via npm and set up your GraphQL schema and resolvers. The deployment and port configuration work the same way as with REST endpoints.

Install the cors npm package and configure it with the specific origin of your frontend Repl: app.use(cors({ origin: 'https://your-frontend.replit.app' })). For the simplest setup, put both frontend and API in the same Repl.

Express defaults to 100KB for JSON bodies. Configure a higher limit with app.use(express.json({ limit: '1mb' })). For file uploads, use multer middleware. The overall app size limit is 8 GB for Autoscale deployments.

Install express-rate-limit via npm and apply it as middleware: app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 })). This limits each IP to 100 requests per 15 minutes.

A 502 usually means the server crashed or did not start in time. Check the Deployments Logs tab for the error. Common causes: missing deployment secrets, server binding to localhost instead of 0.0.0.0, or the health check timing out.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.