How To make a Production grade NEST API Restful Part two

Divyanshu Negiā€¢

ā€¢

šŸš€šŸš€šŸš€šŸš€ 7 min read

PART 2

Welcome back, and here we go and setup some tools we would need to build this application.

Lets start with Prisma

npm install prisma --save-dev

If you are wondering what is prisma and why prisma ?

Prisma is an ORM, well its more than an ORM, its a query builder, in simple terms this will help us manage our database queries, we can visualise our complete database in 1 file called prisma.schema and can use Prisma apis to interact with our database very easily.

Its my goto ORM and unless you have no other strong reason you cannot go wrong with it.

Setting up the database

I am choosing Postgresql as my database, its open source, its best in the industry to work with a relational database, I was always scared with migrations and relationships with a relational database but using something like Prisma makes it super simple to use.

Its always safe in terms of development to work with SQL, IMO it makes a strict table and schema earlier, so thought is given early in development how the data would look like, but if you dont know how the data would grow and be handled as the app grows NO-SQL wins as no such major pre planning required.

āœ… Prisma Installed

lets setup prisma

npx prisma init

āœ… Prisma Schema created

once you init the prisma a prisma.schema file is created, this is how my updated prisma.schema file looks like.

I have 2 models, 1 is to manage users and another to manage the tokens created by the user to access the API, the idea here is anytime a user has to access the API, it will make sure the token is valid.

we can revoke the token and also regenerate a token for the user as well.

model LcUser {
uid String @id
name String?
email String?
phone String?
state VerificationState
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
APIToken APIToken[]
}

model APIToken {
id String @id
token String @unique
user LcUser @relation(fields: [userId], references: [uid])
userId String
tokenState TokenState
valdity Int @default(1)
reason String
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
}

enum VerificationState {
PENDING
VERIFIED
DELETED
}

enum TokenState {
ACTIVE
INACTIVE
}

āœ… Running Postgres locally

I will be setting up postgres locally on a local docker container, this makes it super easy and also makes deployment easier, to do this, I will only have to write a docker-compose file, here is how my file looks like :

version: "3.9"
services:
db:
image: "postgres:14.1-alpine"
ports:
- "5432:5432"
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=litecode
restart: always
volumes:
- db:/var/lib/postgresql/data
volumes:
db:

After setting this Docker compose file our postgres DB would be running locally with this command

docker compose up -d

-d is optional when we want our docker to run in background.

This would spin up a docker container, install a postgres 14.1-alpine version and host it at 5432 port and map it to 5432 port of our local machine. also the username password and database name is provided as part of the environment variables.

Now update your .env file as well.

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/litecode?schema=public"

now our DB is running and we can start the prisma magic.

npx prisma db push

This would start creating the Prisma client and also populate our table schema in the database running.

All good so far.

npx prisma studio

This would start the Prisma studio at port 5555 where we can check the tables created in our postgres database.

Now some refactoring and updating all the steps into 1 command.

What I want is, if I share this repo with any other developer he should do the following steps

  1. Clone the repo
  2. run yarn setup

and thats it. everything should start working for him automatically.

So let's fix that.

Making setup with 1 command setup

I have written this shell script which does the following steps

  1. It checks if you have docker installed
  2. If you have docker it checks if the daemon is running
    1. In Mac OS it starts the docker app
    2. waits for it to reboot
  3. Once Docker up and running it runs the docker compose command.
#!/bin/bash
cecho(){
RED="\033[0;31m"
GREEN="\033[0;32m" # <-- [0 means not bold
YELLOW="\033[1;33m" # <-- [1 means bold
CYAN="\033[1;36m"
NC="\033[0m" # No Color
printf "${!1}${2} ${NC}\n" # <-- bash
}
if [ -x "$(command -v docker)" ]; then
cecho "GREEN" "āœ… Docker is present, continuing..."
if (! docker stats --no-stream 2>/dev/null); then
# On Mac OS this would be the terminal command to launch Docker
open /Applications/Docker.app
echo -n "Waiting for Docker to launch"
sleep 1
# Wait until Docker daemon is running and has completed initialisation
while (! docker stats --no-stream >/dev/null 2>&1); do
# Docker takes a few seconds to initialize
echo -n "."
sleep 1
done
fi
echo
cecho "YELLOW" "Docker started"
cecho "GREEN" "Docker compose up db -d"
docker compose up db -d
cecho "GREEN" "Docker Image running"
else
cecho "RED" "āš ļø Docker is missing, Please Install Docker and make sure Docker daemon is running"
fi

Now the next step is to add a script in our package.json

"setup": "./setup.sh"

You might get a permission issue when running the shell script, in that case locally just set the script file permission using this command sudo chmod +x setup.sh

ok, we are good, now any contributor can clone the repo, run setup and if docker installed, it will run the postgresql db.

lets add the prisma setup part of the same command as well, so updating the script to

"setup": "./setup.sh && yarn && yarn prisma:push && yarn prisma:studio",
"migrate:dev": "prisma migrate dev",
"migrate:deploy": "prisma migrate deploy",
"migrate:reset": "prisma migrate reset",
"migrate:resolve": "prisma migrate resolve",
"prisma:generate": "prisma generate",
"prisma:studio": "prisma studio",
"prisma:push": "prisma db push",

all the common prisma commands made smaller and added in the package.json file.

Now the setup command is looking good, it will setup the docker, postgresql, install node modules, setup tables and run prisma studio to check for our db in local browser.

Lets seed the data. šŸŖ“

Seeding local development data

Now the next step is, we need some data to be in the tables created locally, so that when we setup the db its not empty, this will help us run some local e2e tests, can also help in development with some default data already in the tables.

lets create a file called seed.ts in the prisma folder.

also install ts-node, this would be required to run the seed file independently

yarn add ts-node

To use npm and yarn or pnpm is an individual decision, make sure you understand the pros and cons of either of the approach, I would prefer yarn as its been pretty handy for me since few years, never faced any major issues also compared to npm is quite fast due to parallel dependency resolutions

here is how the seed.ts looks like for me

import { APIToken, LcUser, PrismaClient, TokenState, VerificationState } from "@prisma/client";
import { v4 } from "uuid";

const prisma = new PrismaClient();

async function main() {
console.log("Seeding...");
await prisma.$connect();
const userDummy: LcUser = {
uid: "id_" + v4(),
name: "Divyanshu Negi",
email: "div@litecode.dev",
phone: "+91999888989",
state: VerificationState.VERIFIED,
createdAt: new Date(),
updatedAt: new Date()
};

const tokenDummyEntry: APIToken = {
token: "api." + v4(),
createdAt: new Date(),
updatedAt: new Date(),
id: v4(),
userId: userDummy.uid,
tokenState: TokenState.INACTIVE,
reason: "expired",
valdity: 1
}

await addDummyUser(userDummy);
await addDummyTokens(userDummy.uid, tokenDummyEntry);
}

const addDummyUser = async (user: LcUser) => {
console.log("Adding dummy user...");
await prisma.lcUser.create({
data: { ...user },
});
};

const addDummyTokens = async (userId: string, tokenDummyEntry: APIToken) => {
console.log("Adding dummy tokens...");
for (let i = 0; i < 5; i++) {
tokenDummyEntry.id = v4();
tokenDummyEntry.userId = userId;
tokenDummyEntry.token = "api." + v4();
tokenDummyEntry.tokenState = i == 4 ? TokenState.ACTIVE : TokenState.INACTIVE;
await prisma.aPIToken.create({
data: { ...tokenDummyEntry },
});
}
}

main()
.catch((e) => console.error(e))
.finally(async () => {
await prisma.$disconnect();
});

now to seed the database run this command

npx ts-node prisma/seed.ts  

This will run and all the data in the postgres tables will be populated with dummy entries, which you can see using prisma studio.

now lets add the seed command as part of our setup step, so once we setup some data is also present in our locally running database

updated this script in package.json

"setup": "./setup.sh && yarn && yarn prisma:push && yarn seed && yarn prisma:studio",
"seed":"ts-node prisma/seed.ts",

cool, now with 1 setup command we are good to go and start the development without worrying about database, docker or data in tables.

Local VSCode Settings

One setting which I always want when working on a JS project is, to set some default rules which everyone should use.

and vscode provides this handy way to manage that.

lets create a .vscode folder in the root directory of our project add a setting.json file in this folder and add this data

{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"eslint.validate": ["typescript"]
}

here we are setting some default setting when working on a vscode editor, make sure these changes are also pushed to VCS (github).

Now when we save a file automatically the formatting works and updates the file.

This is it for the second part, we are very close to make a production ready nestJS project.

In the next part we will be taking a look at

  • Setting github actions to run workflows to check lint
  • making our very first API endpoint and deploy it with 1 command when we push
  • Serverless configurations to host our endpoints on AWS Lambda
  • Hosting our Postgresql database

Thanks, will see you soon.

X

Did this post help you ?

I'd appreciate your feedback so I can make my blog posts more helpful. Did this post help you learn something or fix an issue you were having?

Yes

No

X

If you'd like to support this blog by buying me a coffee I'd really appreciate it!

X

Subscribe to my newsletter

Join 107+ other developers and get free, weekly updates and code insights directly to your inbox.

  • No Spam
  • Unsubscribe whenever
  • Email Address

    Powered by Buttondown

    Picture of Divyanshu Negi

    Divyanshu Negi is a VP of Engineering at Zaapi Pte.

    X