The Complete Node.js Backend Setup Guide: TypeScript + Prisma + PostgreSQL + Docker (2025)

The Complete Node.js Backend Setup Guide: TypeScript + Prisma + PostgreSQL + Docker (2025)

5 min read
17
nodejsdockerpostgresqlprismatypescript

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.