# How to build microservices with Replit

- Tool: Replit
- Difficulty: Beginner
- Time required: 20 minutes
- Compatibility: Core, Pro, and Enterprise plans (Autoscale deployment requires a paid plan)
- Last updated: March 2026

## 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.

## Before you start

- 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.

```
# Create the project structure in Shell
mkdir -p src/routes src/middleware src/utils
touch src/index.js
touch src/routes/users.js
touch src/routes/products.js
touch src/routes/orders.js
touch src/middleware/auth.js
touch src/middleware/errorHandler.js
touch 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.

```
// src/index.js
const express = require('express');
const cors = require('cors');
const { initDatabase } = require('./utils/db');
const usersRouter = require('./routes/users');
const productsRouter = require('./routes/products');
const ordersRouter = require('./routes/orders');
const errorHandler = require('./middleware/errorHandler');

const app = express();
const PORT = process.env.PORT || 3000;

// Global middleware
app.use(cors());
app.use(express.json());

// Health check
app.get('/', (req, res) => {
  res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});

// Mount route modules
app.use('/api/users', usersRouter);
app.use('/api/products', productsRouter);
app.use('/api/orders', ordersRouter);

// Error handler (must be last)
app.use(errorHandler);

app.listen(PORT, '0.0.0.0', async () => {
  await initDatabase();
  console.log(`API running on 0.0.0.0:${PORT}`);
});
```

**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.

```
// src/routes/products.js
const express = require('express');
const { query } = require('../utils/db');
const router = express.Router();

// GET /api/products
router.get('/', async (req, res, next) => {
  try {
    const result = await query(
      'SELECT * FROM products ORDER BY created_at DESC LIMIT 50'
    );
    res.json(result.rows);
  } catch (err) {
    next(err);
  }
});

// POST /api/products
router.post('/', async (req, res, next) => {
  try {
    const { name, price, description } = req.body;
    if (!name || !price) {
      return res.status(400).json({ error: 'name and price are required' });
    }
    const result = await query(
      'INSERT INTO products (name, price, description) VALUES ($1, $2, $3) RETURNING *',
      [name, price, description]
    );
    res.status(201).json(result.rows[0]);
  } catch (err) {
    next(err);
  }
});

// GET /api/products/:id
router.get('/:id', async (req, res, next) => {
  try {
    const result = await query(
      'SELECT * FROM products WHERE id = $1',
      [req.params.id]
    );
    if (result.rows.length === 0) {
      return res.status(404).json({ error: 'Product not found' });
    }
    res.json(result.rows[0]);
  } catch (err) {
    next(err);
  }
});

module.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.

```
// src/middleware/auth.js
function requireApiKey(req, res, next) {
  const apiKey = req.headers['x-api-key'];
  const validKey = process.env.API_KEY;

  if (!validKey) {
    console.error('API_KEY not configured in Secrets');
    return res.status(500).json({ error: 'Server configuration error' });
  }

  if (!apiKey || apiKey !== validKey) {
    return res.status(401).json({ error: 'Invalid or missing API key' });
  }

  next();
}

module.exports = { requireApiKey };

// Usage in index.js:
// 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.

```
// src/middleware/errorHandler.js
function errorHandler(err, req, res, next) {
  console.error(`[${new Date().toISOString()}] Error:`, err.message);
  console.error('Stack:', err.stack);

  // PostgreSQL constraint violations
  if (err.code === '23505') {
    return res.status(409).json({ error: 'Resource already exists' });
  }
  if (err.code === '23503') {
    return res.status(400).json({ error: 'Referenced resource does not exist' });
  }

  // Default server error
  res.status(err.status || 500).json({
    error: process.env.NODE_ENV === 'production'
      ? 'Internal server error'
      : err.message
  });
}

module.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.

```
# .replit deployment configuration
[deployment]
build = ["npm", "install"]
run = ["node", "src/index.js"]
deploymentTarget = "cloudrun"

[[ports]]
localPort = 3000
externalPort = 80
```

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

## Complete code example

File: `src/index.js`

```javascript
// src/index.js — API gateway for a modular Replit backend
const express = require('express');
const cors = require('cors');
const { initDatabase } = require('./utils/db');
const { requireApiKey } = require('./middleware/auth');
const errorHandler = require('./middleware/errorHandler');

const app = express();
const PORT = process.env.PORT || 3000;

// Global middleware
app.use(cors());
app.use(express.json({ limit: '1mb' }));

// Request logging
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.path} ${res.statusCode} ${duration}ms`);
  });
  next();
});

// Health check (public, no auth)
app.get('/', (req, res) => {
  res.json({ status: 'healthy', version: '1.0.0' });
});

// Protected API routes
const usersRouter = require('./routes/users');
const productsRouter = require('./routes/products');
const ordersRouter = require('./routes/orders');

app.use('/api/users', requireApiKey, usersRouter);
app.use('/api/products', requireApiKey, productsRouter);
app.use('/api/orders', requireApiKey, ordersRouter);

// Centralized error handler
app.use(errorHandler);

// Start server
app.listen(PORT, '0.0.0.0', async () => {
  try {
    await initDatabase();
    console.log(`API gateway running on 0.0.0.0:${PORT}`);
    console.log('Routes: /api/users, /api/products, /api/orders');
  } catch (err) {
    console.error('Failed to initialize:', err.message);
    process.exit(1);
  }
});
```

## Common mistakes

- **Putting all API routes in a single file that grows to hundreds of lines** — undefined Fix: Split routes into separate files per domain and mount them with app.use('/api/resource', router)
- **Handling errors individually in every route with duplicate try-catch logic** — undefined Fix: Use next(error) in routes and create a centralized error handler middleware with four parameters
- **Binding the server to localhost instead of 0.0.0.0, causing 'an open port was not detected' on deploy** — undefined Fix: Always use app.listen(PORT, '0.0.0.0') in Express to accept external connections
- **Forgetting to add the API_KEY secret in the Deployments pane, breaking auth in production** — undefined Fix: 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

## Frequently asked questions

### Can I run true microservices (separate processes) on Replit?

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.

### How does Autoscale handle traffic spikes?

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.

### How do I test my API endpoints during development?

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.

### Can I use GraphQL instead of REST on Replit?

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.

### How do I handle CORS when my frontend and API are on different Repls?

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.

### What is the maximum request size my Replit API can handle?

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.

### How do I add rate limiting to my Replit API?

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.

### Why does my API return 502 Bad Gateway after deploying?

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.

---

Source: https://www.rapidevelopers.com/replit-tutorial/how-to-set-up-an-api-gateway-for-microservices-using-replit-as-the-backend
© RapidDev — https://www.rapidevelopers.com/replit-tutorial/how-to-set-up-an-api-gateway-for-microservices-using-replit-as-the-backend
