DEV Community

Cover image for Dockerize PERN-TypeScript App Using Prisma ORM With Docker Compose
Abdul Ahad Abeer
Abdul Ahad Abeer

Posted on

Dockerize PERN-TypeScript App Using Prisma ORM With Docker Compose

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
Enter fullscreen mode Exit fullscreen mode

Code Backend

Create a node.js-express.js app first:

mkdir backend && cd backend
npm init -y
Enter fullscreen mode Exit fullscreen mode

Install all the necessary dependencies from npm:

npm install express dotenv cors
Enter fullscreen mode Exit fullscreen mode

Install Typescript related dev dependencies as well:

npm install --save-dev typescript ts-node @types/express @types/node @types/cors
Enter fullscreen mode Exit fullscreen mode

Generate tsconfig.json file:

tsc --init
Enter fullscreen mode Exit fullscreen mode

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"]
}
Enter fullscreen mode Exit fullscreen mode

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
--- ...
Enter fullscreen mode Exit fullscreen mode

Integrate Postgres With Prisma

Install Prisma and Prisma Client first:

npm i @prisma/client
npm i --save-dev prisma
Enter fullscreen mode Exit fullscreen mode

Generate a prisma folder:

npx prisma init
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

Set DATABASE_URL in the .env file:

DATABASE_URL=postgresql://postgres:postgres@db:5432/pern_db?schema=public
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}`)
})

Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

To exclude node_modules, create a .dockerignore file:

node_modules
Enter fullscreen mode Exit fullscreen mode

Code Frontend

Create the frontend:

npm create vite@latest frontend -- --template react-ts
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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" ]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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: {}
Enter fullscreen mode Exit fullscreen mode

Run this command:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Migrate the prisma schema:

docker backend -it backend npx prisma migrate dev --name init
Enter fullscreen mode Exit fullscreen mode

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

Postgres on Neon - Get the Free Plan

No credit card required. The database you love, on a serverless platform designed to help you build faster.

Get Postgres on Neon

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay