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
Top comments (0)