Dockerize PERN-TypeScript App Using Prisma ORM With Docker Compose
Abdul Ahad Abeer
Posted on October 16, 2024
Introduction
This article won't go into detailed explanations. Instead, I’ll just provide the code snippets you need to dockerize a PERN stack application. If you want a more detailed explanation, check out this article where I’ve covered everything in more depth.
The Project Structure
pern-project/
--- frontend/
------ .dockerignore
------ frontend.dockerfile
------ ...
--- backend/
------ .dockerignore
------ backend.dockerfile
------ ...
--- docker-compose.yml
Code Backend
Create a node.js-express.js app first:
mkdir backend && cd backend
npm init -y
Install all the necessary dependencies from npm:
npm install express dotenv cors
Install Typescript related dev dependencies as well:
npm install --save-dev typescript ts-node @types/express @types/node @types/cors
Generate tsconfig.json
file:
tsc --init
Replace everything inside tsconfig.json
with this piece of code in the following:
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts", "data-types.d.ts"]
}
Create src
folder and index.ts
file inside src
folder. So, now the backend folder structure:
backend/
--- node_modules/
--- src/
------ index.ts
--- package.json
--- tsconfig.json
--- ...
Integrate Postgres With Prisma
Install Prisma and Prisma Client first:
npm i @prisma/client
npm i --save-dev prisma
Generate a prisma folder:
npx prisma init
Write a User model in the schema.prisma file:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
name String
username String
email String
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("users")
}
Set DATABASE_URL
in the .env file:
DATABASE_URL=postgresql://postgres:postgres@db:5432/pern_db?schema=public
Create a file prismadb.ts in the src folder:
import { PrismaClient } from "@prisma/client"
import "dotenv/config"
// Extend the global object with PrismaClient
declare global {
var prisma: PrismaClient | undefined
}
// Prevent multiple instances of Prisma Client in development
const prisma = global.prisma || new PrismaClient()
if (process.env.NODE_ENV !== "production") global.prisma = prisma
export default prisma
Define /register
endpoint in the index.ts
file. The index.ts
file:
import express, { Request, Response } from "express"
import "dotenv/config"
import cors from "cors"
import { corsOptions } from "./constants/config"
const app = express()
const PORT = process.env.PORT || 3000
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.use(cors({
const corsOptions = {
origin: process.env.CLIENT_URL || 'http://localhost:5173',
credentials: true,
}
}))
app.get("/", (req: Request, res: Response) => {
res.json({
message: "Hello, TypeScript with Express! Updated!",
})
})
app.post("/register", async (req: Request, res: Response) => {
const { name, username, email, password } = req.body
await prisma.user.create({
data: {
name,
username,
email,
password,
},
})
res.json({
message: "User created successfully",
})
})
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`)
})
Backend Dockerfile
Create a file naming backend.dockerfile in the root of the backend directory and write:
FROM node:20
WORKDIR /app
COPY package*.json .
RUN npm install
COPY prisma ./prisma
RUN npx prisma generate
COPY . .
EXPOSE 3000
RUN npm install -g nodemon ts-node
CMD ["nodemon", "src/index.ts"]
To exclude node_modules
, create a .dockerignore file:
node_modules
Code Frontend
Create the frontend:
npm create vite@latest frontend -- --template react-ts
Make API call in the React app:
// App.tsx
import { FormEvent, useEffect, useState } from "react"
....
function App() {
const [name, setName] = useState("")
const [username, setUsername] = useState("")
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const saveUser = async (e: FormEvent) => {
e.preventDefault()
await fetch("http://localhost:3000/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name,
username,
email,
password,
}),
})
.then((res) => res.json())
.then((data) => console.log(data))
}
useEffect(() => {
fetch("http://localhost:3000")
.then((res) => res.json())
.then((data) => console.log(data))
}, [])
return (
<>
<form onSubmit={saveUser}>
<input
type="text"
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
<input
type="text"
onChange={(e) => setUsername(e.target.value)}
placeholder="Enter your username"
/>
<input
type="email"
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
/>
<input
type="password"
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your password"
/>
<button type="submit">Submit</button>
</form>
</>
)
}
export default App
Frontend Dockerfile
Create a file naming frontend.dockerfile in the root of the frontend directory and write:
FROM node:20
WORKDIR /app
COPY package*.json .
RUN npm install
EXPOSE 5173
COPY . .
CMD [ "npm", "run", "dev" ]
Docker-Compose file
Add the following in the .env
file of the backend folder:
// backend/.env
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=pern_db
DATABASE_URL=postgresql://postgres:postgres@db:5432/pern_db?schema=public
The docker-compose.yml file:
services:
frontend:
container_name: frontend
build:
context: ./frontend
dockerfile: frontend.dockerfile
ports:
- "5173:5173"
networks:
- pern_net
volumes:
- ./frontend:/app
- /app/node_modules
depends_on:
- server
backend:
container_name: backend
build:
context: ./backend
dockerfile: backend.dockerfile
env_file:
- ./backend/.env
ports:
- "3000:3000"
networks:
- pern_net
volumes:
- ./backend:/app
- /app/node_modules
depends_on:
- db
db:
container_name: db
image: postgres:14
restart: always
ports:
- "5432:5432"
networks:
- pern_net
env_file:
- ./backend/.env
volumes:
- pgdata:/data/db
networks:
pern_net:
driver: bridge
volumes:
pgdata: {}
Run this command:
docker compose up -d
Migrate the prisma schema:
docker backend -it backend npx prisma migrate dev --name init
Posted on October 16, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.