Building a simple REST API using Nodejs, Expressjs, Prisma, and SQL - Part 03
Setting up TypeScript and Validation with Zod
In the last two articles, we dealt with setting up a MySQL database on PlanetScale and creating an Express API to do CRUD operations on the User table in our database. Now we will add TypeScript to our project.
Let's begin by installing the following packages:
npm i -D typescript ts-node @types/express @types/node
After installing the dev dependencies, run the following command on the terminal
npx tsc --init
The above command will create a tsconfig.json
file.
Open the file and first search for 'outDir' in the file. And uncomment it. And change the value to ./dist. Now the line should look like this:
'outDir': './dist',
Then in the same file search for 'rootDir', and give it the value of './src'. The line will look like this:
'rootDir': './src',
Okay, now we have the needed configuration to get started. Next, let's change our index.js which is Javascript to index.ts to use TypeScript.
Also, we will create a folder named src in the root folder of our project. And then we will move our index.ts to src.
In the terminal let's run the command npx tsc
to see our index.ts file which is in the src folder being compiled to index.js in the dist directory. The dist directory is auto-generated when we run npx tsc command.
We won't touch index.js in the dist directory. We will be working on the index.ts file in our src folder. The index.js file will be to run the server.
To run the server we can now use node ./dist/index.js
in the terminal.
Give it a shot and your server will run as expected.
Now let's make a few changes in the package.json file. First, we can remove the "type": "module"
we had added in our last article.
Then inside scripts add a build and start command. Also, make changes to the dev command.
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "npx tsc",
"dev": "npx tsc -w & nodemon ./dist/index.js",
"start": "node ./dist/index.js"
},
What we did is we added the command we used earlier to compile our index.ts to index.js as a npm script command. We also added a dev command and a start command. Again start command is the one we saw earlier. In dev command, we are using tsc to compile and listen on watch mode while also running nodemon parallelly to serve index.js file from ./dist directory.
Now you can run the command npm run dev
to have the compilation and live serving happen as you make changes to your code.
We will now make changes to index.ts to use TypeScript features.
import express, { type Application, type Request, type Response } from 'express'
const app: Application = express()
router.get('/users', async (req: Request, res: Response) => {
try {
const users = await prisma.user.findMany()
res.status(200).json(users)
} catch (err) {
res.status(404).json('Items not found')
}
})
By adding the package @types/express
in the previous steps, we now have types available from express. We can extract Application type and it as a type to our app variable. Then for req and res, we can add the types, Request and Response correspondingly.
That's it we now have TypeScript added to our project.
Now we will add validation by using the schema declaration and validation library Zod.
Let's install it by using the command npm install zod
and then create a new file in our src directory named user_model.ts.
import { z } from 'zod'
export const CreateUserSchema = z.object({
username: z.string(),
})
export type User = z.infer<typeof CreateUserSchema>
In the above code, we are first importing zod and using it to create a CreateUserSchema. We have the field username in there, which is of z.string(). Then we infer the type from CreateUserSchema to the type User.
Now let's get back to index.ts. Import both the schema we created and the type User.
import { CreateUserSchema, User } from './user_model.js'
const validateSchema = (schema: z.Schema<any>) => (req: Request, res: Response, next: NextFunction) => {
try {
// Validate and parse the request body using the schema
schema.parse(req.body);
next();
} catch (e: any) {
res.status(400).json({ error: e.errors });
}
};
router.post('/users', validateSchema(CreateUserSchema.strict()), async (req: Request<{}, {}, User>, res: Response) => {
const newUser = await prisma.user.create({ data: req.body })
res.status(201).json(newUser)
})
After importing the classes, we will write a middleware to do our validation. This middleware code will be executed before the code reaches the body of our post route. If the req.body doesn't match what we have in the CreateUserSchema. The code will not proceed and an error will be sent back. If the validation passes, meaning if the req.body matches CreateUserSchema, the next() will tell the code to proceed with the code inside the post route. This validation comes from Zod.
Then we update the post route, to use the validation middleware by passing it after the path and before the req, res callback. Inside validateSchema function we are passing in our CreateUserSchema. We have also added .strict() at the end of the CreateUserSchema so that it throws the error and doesn't parse req.body and proceed with next().
With that, we have added TypeScript to our REST API project and have added validation of the request body with Zod.