Introduction
Recently, I built a backend using Express, Postgres, and Prisma ORM — and honestly, it was one of the most satisfying parts of my full-stack journey so far.
I always heard people say "just use Prisma, it's easy," but the magic clicked only when I actually connected everything myself. Creating the file structure, wiring controllers → services → Prisma, and watching real data flow from the database felt amazing.
This blog is my process, what I learned, and how I structured everything.
Setting Up Prisma & the Project
I started with a simple Express project:
npm init -y
npm install express prisma @prisma/client
npx prisma init
Prisma created a schema.prisma file where I defined my first model:
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
}
Then I pushed it to my database:
npx prisma migrate dev --name init
At this point, Postgres had real tables, and Prisma generated a fully typed client. That part felt like magic — everything was suddenly strongly typed and auto-suggested in VS Code.
Structuring My Express Backend
I decided on a clean structure early on:
src/
controllers/
services/
routes/
prisma/
index.ts
This made everything feel modular instead of dumping all logic in one file.
- Controllers → handle requests & responses
- Services → talk to Prisma and contain business logic
- Routes → map HTTP endpoints
- Prisma client → a single instance shared across the project
My prisma.ts file looked like this:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default prisma;
Creating the Service Layer
The service layer was the first moment things "clicked."
It felt clean, reusable, and easy to test.
// services/userService.ts
import prisma from "../prisma/prisma";
export const getAllUsers = () => {
return prisma.user.findMany();
};
export const createUser = (data: { name: string; email: string }) => {
return prisma.user.create({ data });
};
This file is where the real work happens — Prisma queries, validation, transformations, etc.
Writing Controllers
Controllers sit between Express and the service layer:
// controllers/userController.ts
import { Request, Response } from "express";
import * as userService from "../services/userService";
export const getUsers = async (req: Request, res: Response) => {
const users = await userService.getAllUsers();
res.json(users);
};
export const addUser = async (req: Request, res: Response) => {
const newUser = await userService.createUser(req.body);
res.status(201).json(newUser);
};
I liked this separation — controllers don't care how things work, just what they return.
Connecting Routes
Finally, I created routes that map endpoints to controllers:
// routes/userRoutes.ts
import { Router } from "express";
import { getUsers, addUser } from "../controllers/userController";
const router = Router();
router.get("/", getUsers);
router.post("/", addUser);
export default router;
Then wired them up in index.ts:
import express from "express";
import userRoutes from "./routes/userRoutes";
const app = express();
app.use(express.json());
app.use("/users", userRoutes);
app.listen(3000, () => console.log("Server running on port 3000"));
Seeing Data Flow
The moment I hit:
curl http://localhost:3000/users
…and saw Postgres data appear in the terminal?
Peak developer joy.
Same with creating a new user:
curl -X POST -H "Content-Type: application/json" \
-d '{"name":"Satoshi","email":"satoshi@example.com"}' \
http://localhost:3000/users
It worked on the first try — which almost never happens.
My Takeaways
- Prisma makes the database feel approachable — the type-safety and auto-complete are game-changing.
- File structure matters — controllers/services/routes keep things clean and scalable.
- Small wins matter — seeing your first query run successfully is incredibly motivating.
- Express + Prisma is a perfect starter backend — simple, fast, and understandable.
Setting all this up helped me understand the bigger picture of backend architecture and made me feel more confident moving deeper into full-stack development.
And honestly? I can't wait to build more with it.