Deploy an Express + MongoDB REST API to Production (Mongoose, 2026)
Build and deploy a production Express API with MongoDB and Mongoose: MONGODB_URI, validation, error handling, security middleware and graceful shutdown.
- Published
- Reading time
- 3 min
On this page
Key takeaways
- Attach a managed MongoDB database and connect with process.env.MONGODB_URI: it's injected over a private network.
- Connect once at startup and reuse the connection; close it on SIGTERM so zero-downtime deploys stay clean.
- Add helmet, body-size limits and a central error handler before you go live.
Express and MongoDB are still one of the quickest ways to ship a JSON API. This guide builds a small production-ready API with Mongoose and deploys it with a managed MongoDB database.
1. Connect once, from the environment
// db.js
import mongoose from "mongoose";
export async function connect() {
await mongoose.connect(process.env.MONGODB_URI);
console.log("MongoDB connected");
}When you attach a managed MongoDB database to the project, MONGODB_URI is injected into the app's environment and reached over a private network, with no public port to secure. Standard drivers read it unchanged.
2. A model with validation
// models/Todo.js
import mongoose from "mongoose";
const todoSchema = new mongoose.Schema(
{
title: { type: String, required: true, trim: true, maxlength: 200 },
done: { type: Boolean, default: false },
},
{ timestamps: true },
);
export const Todo = mongoose.model("Todo", todoSchema);Schema validation is your first line of defense against bad input. It rejects missing or oversized fields before they reach the database.
3. The API
// server.js
import express from "express";
import helmet from "helmet";
import mongoose from "mongoose";
import { connect } from "./db.js";
import { Todo } from "./models/Todo.js";
const app = express();
app.use(helmet());
app.use(express.json({ limit: "100kb" }));
app.get("/health", (_, res) => res.json({ ok: mongoose.connection.readyState === 1 }));
app.get("/todos", async (_, res) => res.json(await Todo.find().sort({ createdAt: -1 }).limit(100)));
app.post("/todos", async (req, res) => {
const todo = await Todo.create({ title: req.body.title });
res.status(201).json(todo);
});
// Central error handler: validation errors → 400, everything else → 500.
app.use((err, _req, res, _next) => {
if (err instanceof mongoose.Error.ValidationError) return res.status(400).json({ error: err.message });
console.error(err);
res.status(500).json({ error: "Internal error" });
});
await connect();
const server = app.listen(process.env.PORT || 3000);
// Finish in-flight requests and close the DB connection when the platform stops the old release.
process.on("SIGTERM", () => server.close(() => mongoose.disconnect().then(() => process.exit(0))));In Express 5, errors thrown inside async handlers reach the error handler automatically.
{
"type": "module",
"scripts": { "start": "node server.js" },
"dependencies": { "express": "^5", "helmet": "^8", "mongoose": "^8" }
}4. Deploy
curl -fsSL https://cli.dockhosting.dev/install.sh | sh # Windows: irm https://cli.dockhosting.dev/install.ps1 | iex
dock login
dock deployExpress is detected from package.json, dependencies are installed with the package manager that matches your lockfile, and the app starts with npm start. Attach a MongoDB database in the dashboard, run dock restart, and check https://<project>.dockhosting.cloud/health.
5. Before real users arrive
- Indexes: add indexes for the fields you query on, such as
todoSchema.index({ createdAt: -1 }). - Rate limiting: add a limiter (attach managed Redis and use
REDIS_URLif you run several instances). - Backups: paid plans back up databases daily (kept 3, 7 or 30 days).
- Always on: Free apps sleep after an hour idle. Hobby ($4/month) keeps the API awake.
Prefer SQL? The same flow works with PostgreSQL and DATABASE_URL: see connecting to managed PostgreSQL. New to deploying Node.js? Start with one-command deploys.
Frequently asked questions
Is there a free MongoDB database?
Yes. DockHosting's Free plan includes one managed database, which can be MongoDB, with 500 MB of storage that doesn't pause or expire. Daily backups start on Hobby.
Can I connect to the database with MongoDB Compass from my laptop?
Public database access is available on Hobby and up. On every plan, your app connects over the private network.
Which Node.js versions are supported?
Node.js 20 and 22 LTS, with npm, pnpm or yarn chosen from your lockfile.
- Express
- MongoDB
- Mongoose
- Node.js
- API
Written by Doughmi ILYAS
Doughmi builds DockHosting, the developer-first platform behind dock deploy: the build system, the managed databases and the dashboard. On this blog: guides to shipping apps without running servers, written with the commands the platform actually ships.
