← HomeSee All Blogs

Connecting Express to Prisma & Postgres

2025-11-01

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.

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

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.

You may also like