Create a Backend project with Node.js, Prisma, Typescript and Docker

Featured Image

Looking for the frontend React project? Click here.

Source - https://github.com/bdcorps/node-backend-typescript-prisma

Step 1: Create a new Node.js project

Create a new Node.js project by:

mkdir saasbase-be
cd saasbase-be
npm init --yes
npm i typescript ts-node dotenv cors express prisma @prisma/client
npm i -D ts-node-dev @types/node @types/express @types/cors

Add a new file called src/index.ts that will serve our Express server:

import express, { Application, Request, Response } from "express";
import bodyParser from "body-parser";

const app: Application = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.get("/", (req: Request, res: Response) => {
  res.send("Healthy");
});

const PORT = process.env.PORT || 8000;

app.listen(PORT, () => {
  console.log(`Server is running on PORT ${PORT}`);
});

Create a tsconfig.json :

{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "outDir": "dist"
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

Update package.json with:

"scripts": {
  "build": "tsc",
  "start": "ts-node index.ts",
  "dev": "ts-node-dev --respawn --transpile-only index.ts"
}

Run with:

npm run dev

This will run in Dev mode.

To compile and run a Production build:

npm run build
npm start

Make sure you have a .gitignore :

/node_modules
.env*

Step 2: Add Prisma for Postgres DB

Run:

npx prisma init

Create a file called prisma/schema.prisma:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  firstName String
  lastName  String
}

In your .env:

DATABASE_URL=postgresql://postgres:postgres@localhost:5432/users

Publish this schema to your DB with:

npx prisma db push

You will notice that there are new tables created.

Add this script to package.json to automatically sync Prisma after we install packages:

 "scripts": {
    ...
    "postinstall": "prisma generate"
 },

Create a global Prisma client

You don't want to keep opening new connections when the application hot reloads.

Create a new file called lib/prisma.ts

import { PrismaClient } from "@prisma/client";

declare global {
  var prisma: PrismaClient | undefined;
}

let prisma: PrismaClient;

if (process.env.NODE_ENV === "production") {
  prisma = new PrismaClient()
} else {
  if (!global.prisma) {
    global.prisma = new PrismaClient()
  }
  prisma = global.prisma
}

export default prisma;

Step 2: Add env variables

Add dotenv to the very top of the src/index.ts file:

import * as dotenv from "dotenv";
dotenv.config();

...

console.log(process.env.SECRET_CODE);

Add to your .env :

SECRET_CODE=1234

You can see the server print out the SECRET_CODE when you run the app.

Step 3: Add CORS

Add CORS in index.ts:

import cors from 'cors';

...

// if you want anyone to be able to connect
app.use(cors({ origin: true }))

// if you want only your frontend running at port 3000 to connect to this backend
app.use(cors({ origin: "<http://localhost:3000>" }))

Step 4: Add API endpoints

import prisma from "./lib/prisma";

// Get all users
app.get("/users", async (req, res) => {
  const users = await prisma.user.findMany();
  res.json(users);
});

// Get a user by id
app.get("/users/:id", async (req, res) => {
  const { id } = req.params;
  const user = await prisma.user.findUnique({
    where: { id: parseInt(id) },
  });
  res.json(user);
});

// Create a new user
app.post("/users", async (req, res) => {
  const { email, firstName, lastName } = req.body;
  const newUser = await prisma.user.create({
    data: { email, firstName, lastName },
  });
  res.status(201).json(newUser);
});

// Update a user
app.put("/users/:id", async (req, res) => {
  const { id } = req.params;
  const { email, firstName, lastName } = req.body;
  const updatedUser = await prisma.user.update({
    where: { id: parseInt(id) },
    data: { email, firstName, lastName },
  });
  res.json(updatedUser);
});

// Delete a user
app.delete("/users/:id", async (req, res) => {
  const { id } = req.params;
  const deletedUser = await prisma.user.delete({
    where: { id: parseInt(id) },
  });
  res.json(deletedUser);
});

Step 4: Try out API with Postman

Install and open up Postman to try out the API locally.

Import this collection by clicking File > Import > Raw Text. Paste the below JSON contents:

{
  "info": {
    "_postman_id": "2f404907-c6b1-4d9f-a5dc-82d68980d2dd",
    "name": "Node.js Backend API",
    "schema": "<https://schema.getpostman.com/json/collection/v2.1.0/collection.json>",
    "_exporter_id": "594120"
  },
  "item": [
    {
      "name": "Get all users",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "<http://localhost:8000/users>",
          "protocol": "http",
          "host": ["localhost"],
          "port": "8000",
          "path": ["users"]
        }
      },
      "response": []
    },
    {
      "name": "Add a new user",
      "request": {
        "method": "POST",
        "header": [],
        "body": {
          "mode": "raw",
          "raw": "{\\n    \\"email\\": \\"sukh@saasbase.dev\\",\\n    \\"firstName\\": \\"Sukh\\",\\n    \\"lastName\\": \\"SaaSBase\\"\\n}",
          "options": { "raw": { "language": "json" } }
        },
        "url": {
          "raw": "<http://localhost:8000/users>",
          "protocol": "http",
          "host": ["localhost"],
          "port": "8000",
          "path": ["users"]
        }
      },
      "response": []
    },
    {
      "name": "Get a user",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "<http://localhost:8000/users/1>",
          "protocol": "http",
          "host": ["localhost"],
          "port": "8000",
          "path": ["users", "1"]
        }
      },
      "response": []
    },
    {
      "name": "Delete a user",
      "request": {
        "method": "DELETE",
        "header": [],
        "url": {
          "raw": "<http://localhost:8000/users/1>",
          "protocol": "http",
          "host": ["localhost"],
          "port": "8000",
          "path": ["users", "1"]
        }
      },
      "response": []
    }
  ]
}

Step 4: Add gitignore

Make sure to add to .gitignore:

.env
node_modules

Step 5: Deploy to Heroku

Download Heroku CLI from here.

git init

git add .
git commit -am "first commit"

heroku apps:create saasbase-be
heroku git:remote -a saasbase-be
git push heroku master

heroku open

If you're deploying to Production, continue on.

Step 6: Add Dockerfile for production

FROM node:16 AS builder
WORKDIR /app
COPY package.json ./
RUN npm install

COPY . ./
RUN npm run build

FROM nginx:1.19.0
WORKDIR /usr/share/nginx/html
RUN rm -rf ./*
COPY --from=builder /app/dist .
ENTRYPOINT ["nginx", "-g", "daemon off;"]

docker build -t saasbase-be . --platform linux/amd64 # make sure the build is for correct platform

docker run -p 8000:8000 -d saasbase-be

Step 7: Upload image to Docker Hub

To create a repository:

  • Sign in to Docker Hub.
  • Click Create a repository.
  • Name it sssaini/saasbase-be
docker login

docker tag saasbase-be sssaini/saasbase-be:0.1
docker push sssaini/saasbase-be:0.1

I'm building a new SaaS to automate content marketing for your SaaS

Check it out →

Tools for SaaS Devs