The Complete Node.js Backend Setup Guide: TypeScript + Prisma + PostgreSQL + Docker (2025)
Most Node.js tutorials suck. They show you the basic "hello world" stuff, then disappear when you need to actually connect a database or set up something real.
I've built enough backends to know what works. Here's a setup that won't break on you later.
What we're building
A Node.js backend using:
Express for the server
TypeScript because types save your ass
Prisma for talking to the database
PostgreSQL in Docker (no installing Postgres on your machine)
Takes about 20 minutes. Let's go.
What you need first
Node.js 18 or newer
Docker Desktop (and make sure it's running)
That's it
1. Start the project
mkdir coding-agent-server
cd coding-agent-server
npm init -y
2. Install the packages
The stuff your app needs to run:
npm install express cors dotenv
The dev tools:
npm install -D typescript ts-node-dev @types/node @types/express
3. Set up TypeScript
npx tsc --init
Replace tsconfig.json with this:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"],
"exclude": ["node_modules", "dist", "prisma"]
}
This tells TypeScript where your code lives (src/) and where to put the compiled version (dist/).
4. Create your folders
Make this structure:
coding-agent-server/
├── prisma/
│ └── schema.prisma
├── src/
│ ├── lib/
│ │ └── prisma.ts
│ └── index.ts
├── .env
├── .gitignore
├── docker-compose.yml
├── prisma.config.ts
├── package.json
└── tsconfig.json
Quick command:
mkdir -p src/lib prisma
5. Build the Express server
Create src/index.ts:
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json());
app.get("/health", (_req, res) => {
res.json({ status: "ok" });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Nothing fancy. Just a basic Express server with a health check endpoint.
6. Add npm scripts
In package.json, add these under "scripts":
{
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}
Test it:
npm run dev
Go to http://localhost:3000/health. You should see {"status":"ok"}.
7. Install Prisma
npm install prisma @prisma/client
npx prisma init
This creates a prisma/ folder with a schema file.
8. Set up the Prisma schema
Open prisma/schema.prisma and replace everything with:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
}
model User {
id String @id @default(uuid())
name String
email String @unique
githubId String? @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Don't add url = env("DATABASE_URL") in here. That's old. We handle it differently now.
9. Configure Prisma runtime
Create prisma.config.ts in your root folder:
import "dotenv/config";
import { defineConfig } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: process.env["DATABASE_URL"],
},
});
This is where the database URL actually gets used.
10. Create the Prisma client
Create src/lib/prisma.ts:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export default prisma;
You'll import this file whenever you need to talk to the database.
11. Add environment variables
Create .env:
PORT=3000
DATABASE_URL="postgresql://coding_agent:coding_agent_password@localhost:5432/coding_agent_db"
And add it to .gitignore:
echo ".env" >> .gitignore
12. Set up Postgres with Docker
Create docker-compose.yml:
services:
postgres:
image: postgres:16
container_name: coding-agent-postgres
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_USER: coding_agent
POSTGRES_PASSWORD: coding_agent_password
POSTGRES_DB: coding_agent_db
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Why Docker? Because you don't want to install Postgres on your machine. This keeps everything contained and easy to reset.
13. Start Postgres
docker compose up -d
Check it's running:
docker ps
You should see coding-agent-postgres in there.
14. If things break
If you get weird auth errors, just reset everything:
docker compose down -v
docker compose up -d
This nukes the database and starts fresh.
15. Test the database connection
docker exec -it coding-agent-postgres psql -U coding_agent -d coding_agent_db
If you see coding_agent_db=#, you're good.
Type \q to exit.
16. Create the database tables
npx prisma validate
npx prisma generate
npx prisma migrate dev --name init
This creates your User table in Postgres.
17. Daily workflow
First time (or after restarting your computer):
docker compose up -d
npm run dev
Every other time:
npm run dev
Postgres stays running in Docker, so you just need to start your server.
Using Prisma in your code
Here's how you'd create a user:
import express from "express";
import prisma from "./lib/prisma";
const app = express();
app.use(express.json());
app.post("/users", async (req, res) => {
const { name, email } = req.body;
try {
const user = await prisma.user.create({
data: { name, email },
});
res.json(user);
} catch (error) {
res.status(500).json({ error: "Failed to create user" });
}
});
app.listen(3000);
Why this setup works
You can deploy it anywhere (Render, Railway, fly.io, whatever)
TypeScript catches dumb mistakes before you push to production
Docker means everyone on your team uses the exact same database setup
It's not doing anything weird or clever. Just straightforward stuff that works.
What to add next
Once this is working, you probably want:
Authentication (Clerk or Auth.js work well)
Input validation (use Zod)
Error handling middleware
Logging (Pino is good)
Tests if you're into that
Common problems
"Can't connect to database"
docker compose down -v
docker compose up -d
npx prisma migrate dev
"Can't find @prisma/client"
npx prisma generate
TypeScript complaining about Prisma files
Check that "exclude": ["prisma"] is in your tsconfig.json.
That's it
You now have a working Node.js backend that won't fall apart when you try to add features.
