This post documents how I put together a TypeScript based Nodejs Express backend, wiring middleware, startup scripts, routing structure, and validating the server end-to-end before introducing the coding logic.
Click here for Act 5 · Scene 1
Table of Contents
- Overview
- The index.ts
- Middleware Setup
- Error Handler
- Not Found Middleware
- Startup: Failure
- Fixing It
- Routes and Controllers
- Controllers
- Routes
- Testing with Postman
- Mental Model
- Why this scene matters
Act 5 · Scene 2: From Setup to The First Backend Surface
This scene focuses on establishing a stable backend foundation: a TypeScript Express server that boots cleanly, applies middleware in the right order, exposes its first routes, and can be validated end to end without a frontend.
Overview
With the required Node.js packages installed in the previous scene and version control already in place, the next step was to bring the backend to life.
This scene focuses on initializing the server entry point, configuring core middleware, resolving startup issues, and exposing the first backend surface through structured routes and controllers.
The index.ts
I created a src folder and dropped an index.ts file inside it.
That file is the server’s front door.
Initial imports went in immediately:
import express from "express";
import cors from "cors";
import "dotenv/config";
import { Request, Response } from "express";
import mongoose from "mongoose";
import cookieParser from "cookie-parser";
Just importing some basic packages, no biggie.
Middleware Setup
Before routes, before features, I stopped to wire some middlewares.
A middleware runs between the incoming request and the final response. In Express, it has access to req, res, and next.
Error Handler
Created a middleware folder in the src folder and added errHandlerMiddleware.ts in it.
Here is its content:
import { Request, Response, NextFunction } from "express";
export const errorHandlerMiddleware = (
error: unknown,
req: Request,
res: Response,
_next: NextFunction
) => {
console.log("error message from errorHandlerMiddleware:", error);
res
.status(500)
.json({ msg: error instanceof Error ? error.message : "something went wrong" });
};
Express only recognizes an error handler if it accepts four parameters, even if next is unused.
Not Found Middleware
This middleware handles unmatched routes:
import { Request, Response } from "express";
export const notFoundMiddleware = (req: Request, res: Response) => {
res.status(404).send({ msg: "Route doesn't exist..." });
};
Both middlewares were imported and registered at the bottom of index.ts.
Startup: Failure
With those middlewares in place, I wired up a minimal server configuration:
const app = express();
app.use(cors({ origin: "http://localhost:3000", credentials: true }));
app.use(cookieParser());
app.use(express.json());
app.get("/", (_req: Request, res: Response) => {
res.send("<h1>This is the index.ts route...</h1>");
});
app.use(notFoundMiddleware);
app.use(errorHandlerMiddleware);
const PORT = Number(process.env.PORT) || 5000;
Here's the Startup logic:
const start = async () => {
try {
app.listen(PORT, "localhost", () => {
console.log(`server listening on port ${PORT}`);
});
} catch (error) {
console.log("startupError:", error);
}
};
start();
I then ran:
npm run dev
It resulted in:
npm error Missing script: "dev"
Fixing It
Fixed it by updating package.json:
"scripts": {
"dev": "nodemon src/index.ts",
"build": "tsc",
"start": "npm run build && node dist/index.js"
}
Retried it:
npm run dev
Server started successfully.
Routes and Controllers
Once the server stayed up, I went on to create a controllers folder and a routes folder.
Controllers handle behavior.
Routes decide where requests go.
Controllers
controllers/authController.ts:
export const registerController = (_req: Request, res: Response) => {
res.status(200).json({ msg: "This is the register route" });
};
export const loginController = (_req: Request, res: Response) => {
res.status(200).json({ msg: "This is the login route" });
};
export const logoutController = (_req: Request, res: Response) => {
res.status(200).json({ msg: "This is the logout route" });
};
Routes
routes/auth-route.ts:
const authRouter = Router();
authRouter.post("/register", registerController);
authRouter.post("/login", loginController);
authRouter.get("/logout", logoutController);
export default authRouter;
Testing with Postman
Before introducing the frontend to the backend, I verified backend behavior using Postman.
I then sent Requests:
Here is a picture of postman when first opened:
Made a request to the home route:
Meaning, this line ran:
app.get("/", (_req: Request, res: Response) => {
res.send("<h1>This is the index.ts route...</h1>");
});
I then tested the register route:
Meaning, this line ran:
export const registerController = (_req: Request, res: Response) => {
res.status(200).json({ msg: "This is the register route" });
};
Tested the login route:
Meaning, this line ran:
export const loginController = (_req: Request, res: Response) => {
res.status(200).json({ msg: "This is the login route" });
};
And the logout route followed the same pattern.
Requests hit.
Responses returned successfully.
The server didn't crash
This means that we are ready to rock and roll.
Mental Model
Right now:
The backend boots reliably
Middleware has been introduced
Routes and controllers are separated
The system is ready for real logic
Why This Scene Matters
This scene shows that I:
- Bring backend systems online incrementally
- Build for maintainability
- Don’t rush features before foundations
Thanks for reading.
Let’s move on to the Next Act.
We in the Building…
Building in Progress…




Top comments (0)