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.
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
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
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
Now lets configure the project as typescript project by:
npx tsc -init
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"]
}
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"
}
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"
}
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
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
Depending on what you database schema is, your schema.prisma file would change accordingly, here is what my schema.prisma looks like:
Setting up server folder
create a folder named server
with these files inside of it:
- index.ts where our main app will run
- prisma-client.ts for using prisma client in a singleton pattern
-
server-config.ts for getting data from
.env
to your project
Click on above links to view the content of the files.
This is how the final file structure of the project would look like:
API development
Awesome! Now we are done setting up our project, now its time to start cooking our api!
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;
};
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) => {})
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
});
Lets understand the above router, we can see that in generic params we have written:
-
never
: this specifies that requestparams
will be an empty input(specified by never keyword) -
owner
: this specifies thatresponse
type of the request will be of typeowner
, 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
});
The above route takes the following generic types:
-
params
: object containingowner_id
of typeowner["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!
Top comments (0)