DEV Community

Cover image for Type safe your entire node project with Typescript, Express, and Prisma ORM
Aditya Raj
Aditya Raj

Posted on

Type safe your entire node project with Typescript, Express, and Prisma ORM

We know that without proper knowledge and use of typescript it can circle back to your old JS code, which can become even more difficult to debug and develop.
So in this tutorial, We will create a simple typescript project with Express and Prisma that will use types from a single source of truth, which would be the database schema. So that we can avoid creating bad TS code.

Typescript meme

We will be making a simple express project with REST endpoints for recording Owner to their dogs, endpoints would look like:

Owner Endpoints:
[GET]    /api/owners
[GET]    /api/owners/:id
[GET]    /api/owners/:id/dogs
[POST]   /api/owners
[PUT]    /api/owners/:id
[DELETE] /api/owners/:id

Dog Endpoints:
[GET]    /api/dogs
[GET]    /api/dogs/:id
[POST]   /api/dogs/
[PUT]    /api/dogs/:id
[DELETE] /api/dogs/:id
Enter fullscreen mode Exit fullscreen mode

Link to full github repo: express_ts

Project Setup

Lets start with creating a work folder named express_ts and initializing it as a node project

> mkdir express_ts
> npm init -y
Enter fullscreen mode Exit fullscreen mode

Lets install required dependencies for this project

//Dev dependencies 
> npm i -D typescript @types/express @types/node nodemon prisma ts-node uuid

//Dependencies 
> npm i @prisma/client dotenv express yup
Enter fullscreen mode Exit fullscreen mode

Now lets configure the project as typescript project by:

npx tsc -init
Enter fullscreen mode Exit fullscreen mode

This would generate a file named tsconfig.json that would look like this:

//tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "esModuleInterop": true,
    "target": "es6",
    "moduleResolution": "node",
    "sourceMap": true,
    "outDir": "dist"
  },
  "lib": ["es2015"]
}
Enter fullscreen mode Exit fullscreen mode

The above file simply tells the typescript compiler about the metadata of the project and rules that will be used to compile the project. Know more about tsconfig.json

Add the below code in your "scripts" property in package.json file

{
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "npm run build && cp .env ./dist/.env && cd dist/ && node index.js",
    "db:pull": "npx prisma db pull",
    "build": "npx prisma generate && npx tsc",
    "dev": "npx prisma generate && npx nodemon server/index.ts"
  }
Enter fullscreen mode Exit fullscreen mode

this will help you to run your project without typing extra lines in CLI!

Create nodemon.json file in your root folder and add these code into the file:

{
  "watch": ["server"],
  "ext": "js ts"
}
Enter fullscreen mode Exit fullscreen mode

Great! now you dont have to restart your server everytime you make any changes.

You can view package.json here to know more about the project itself.

I would assume you have setup mysql or any database you prefer, here is how you can setup mysql and mongodb if you haven't

Now we need to setup Prisma for this project

npx prisma init
Enter fullscreen mode Exit fullscreen mode

Create a .env file in your root folder to store sensetive credentials, like server PORT, database connection URL, etc.

Refer here on how to connect your database with prisma, to make this tutorial brief:

This would create a folder named prisma in your root folder contaning file named schema.prisma.

schema.prisma would contain database schema in prisma language that would be used for:

  • Generating types from database schema
  • Allow prisma client to create database query in JS format(without manually writing database query)

Assuming you have already created your database tables, you need to import you schema data, to do that we need to run below command:

npx prisma db pull
Enter fullscreen mode Exit fullscreen mode

Depending on what you database schema is, your schema.prisma file would change accordingly, here is what my schema.prisma looks like:

Schame.prisma

Setting up server folder

create a folder named server with these files inside of it:

Click on above links to view the content of the files.

This is how the final file structure of the project would look like:

file structure

API development

Awesome! Now we are done setting up our project, now its time to start cooking our api!

Hehe boiii

Model Function

We will be creating prisma functions for owner table to perform CRUD operation on the table for the application

//owner.model.ts

import { prisma } from "../prisma-client";
import { owner, Prisma } from "@prisma/client";

const getOwner = async (owner_id: owner["owner_id"]) => {
  const owner = await prisma.owner.findUnique({
    where: {
      owner_id,
    },
  });

  return owner;
};
Enter fullscreen mode Exit fullscreen mode

As we can see owner_id does not have type written manually, instead it is derived from prisma's database schema of owner table.

Also the return type of the above function is of type owner which is created by prisma from the databse schema.

Here we go! now we dont have to go through the process of creating types for every entity, we can now just import them from prisma itself, which makes the development experiance much easier and faster!

See the rest of the model function: owner.model.ts

Typesafe Routes

app.get<Params,ResBody,ReqBody,ReqQuery,Locals>('/api/v1/dogs',(req,res) => {})
Enter fullscreen mode Exit fullscreen mode

In every HTTP method the router provides 5 generic type which can take custom types as input for type safing routes of the app.

Here is how we can type safe our owner.routes.ts:

//owner.routes.ts

import { Prisma, owner } from "@prisma/client";
ownerRouter.get<never, owner[]>("/", async (req, res, next) => {
//your code implementation
});
Enter fullscreen mode Exit fullscreen mode

Lets understand the above router, we can see that in generic params we have written:

  • never: this specifies that request params will be an empty input(specified by never keyword)
  • owner: this specifies that response type of the request will be of type owner, which is again derived by prisma type

Now lets understand a bit complex route:

//This route is used to update owner model(PUT request)

ownerRouter.put<
  { owner_id: owner["owner_id"] },
  owner,
  Prisma.ownerUncheckedUpdateInput
>("/:owner_id", async (req, res, next) => {
//your code implementation
});
Enter fullscreen mode Exit fullscreen mode

The above route takes the following generic types:

  • params: object containing owner_id of type owner["owner_id"]
  • response: owner object
  • request body: It takes a prisma's custom type for update input

Great, now we have learned to typesafe our code with database schema, well obviously to create a production code we need extra types like IBaseApiResponse, IPagination, request validation etc, but that's for another article.

View complete project here.

Hope this tutorial helped, thank you so much!

Thank You

Top comments (0)