Build a production-ready REST API with Node.js and Express — routing, middleware, input validation, error handling, authentication, and database integration.
In this guide
Init: npm init -y && npm install express. Install dev deps: npm install -D nodemon typescript @types/express ts-node. Create src/index.ts: import express from "express"; const app = express(); app.use(express.json()); app.listen(3000, () => console.log("Running on 3000")). Run: npx nodemon src/index.ts. Always use TypeScript for any non-trivial API — it catches type errors before they reach production.
Create src/routes/users.ts: import { Router } from "express"; const router = Router(); router.get("/", getUsers); router.post("/", createUser); router.get("/:id", getUserById); router.put("/:id", updateUser); router.delete("/:id", deleteUser); export default router. In index.ts: app.use("/api/users", userRouter). This separation keeps route files small, testable, and easy to find.
Never trust incoming data. Install: npm install zod. Define a schema: const CreateUserSchema = z.object({ email: z.string().email(), name: z.string().min(2).max(100), password: z.string().min(8) }). In your handler: const parsed = CreateUserSchema.safeParse(req.body); if (!parsed.success) return res.status(400).json({ errors: parsed.error.flatten() }). Zod returns human-readable error messages per field, perfect for form validation responses.
Create an error handler middleware: app.use((err: Error, req, res, next) => { console.error(err); res.status(500).json({ error: process.env.NODE_ENV === "production" ? "Internal server error" : err.message }); }). Register it last, after all routes. For async route handlers, wrap them: const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next). This catches promise rejections and passes them to your error handler.
Install: npm install jsonwebtoken bcryptjs. On login: const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: "7d" }). Create auth middleware: const authMiddleware = (req, res, next) => { const token = req.headers.authorization?.split(" ")[1]; if (!token) return res.status(401).json({ error: "Unauthorized" }); try { req.user = jwt.verify(token, process.env.JWT_SECRET); next(); } catch { res.status(401).json({ error: "Invalid token" }) } }. Apply to protected routes: router.get("/profile", authMiddleware, getProfile).
Need Help?
Our engineering team handles implementations like this every week. Get a free scoping call — we will tell you exactly what it takes and what it costs.
Book a free callCompetitive Intelligence
Efficiency Modeling
© 2026 NexWorldTech — Built for Global Dominance.