Benefits of using Typescript with ExpressJS with Project Template

sumitbhanushali

Sumit Bhanushali

Posted on November 25, 2023

Benefits of using Typescript with ExpressJS with Project Template

In this post, we will setup our Backend service. We will be using ExpressJS for APIs, MySQL for storage and Prisma for ORM. We will be using Typescript as primary language. We will be using Docker to host MySQL on our machine. This will only work on Node Version: v20.10.0 and above.

Why Typescript?

TypeScript enhances Express.js web development by introducing static typing, catching potential bugs early and improving code maintainability. With superior IDE support and modern JavaScript features, TypeScript offers a productive development experience.TypeScript with Express.js combines the simplicity of Express.js with TypeScript's static typing benefits, resulting in more reliable and maintainable web applications.

Our project will be simple blog application named bloggo.

mkdir bloggo && cd bloggo && touch README.md && echo -e "# Bloggo \n\nSimple Blog Application" > README.md

npm init -y
Enter fullscreen mode Exit fullscreen mode

To Add Typescript support

npm i -D typescript @types/node ts-node

npx tsc --init --rootDir ./ --outDir .dist --target es2020 --allowJs --noImplicitAny false

npm i -g nodemon
Enter fullscreen mode Exit fullscreen mode

Here we have installed 3 packages as dev dependencies, their functionality is:

  1. typescript: this package is required to transpile ts to js
  2. @types/node: this provides type definitions like Request, Response for NodeJS
  3. ts-node: this is combination of two package ts transpiles given ts files to js files and node executes those js files

we use tsc package to generate tsconfig.json for us. We need tsconfig.json file to instruct how to handle our ts files

  • --rootDir: location to all ts files
  • --outDir: location to place transpiled js files
  • --target: javascript version for js files to be generated
  • --allowJs: whether it should also allow js files, if disabled it will only allow ts files
  • --noImplicitAny: if true, will not allow any types to have ambigious 'any' declaration

nodemon will be used to watch our files and refresh automatically when any changes detected.

Next, we will install express and @types/express for building APIs

npm i express
npm i -D @types/express
Enter fullscreen mode Exit fullscreen mode

Let's create a basic server which will just respond with Hello World on port 3000

//index.ts

import express, { Request, Response } from 'express';

const app = express();
const port = 3000;

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

app.listen(port, () => {
    console.log(`listening on ${port}`);
})
Enter fullscreen mode Exit fullscreen mode

We will run this using nodemon we had previously installed, we will add some npm scripts in package.json to simplify our efforts

// code elided

"scripts": {
    "start:dev": "nodemon --exec node -r ts-node/register --env-file=config.env index.ts",
    "build": "tsc",
    "build:watch": "tsc --watch"
}

// code elided
Enter fullscreen mode Exit fullscreen mode

- start:dev: we need to use ts-node we had installed previously to run ts files without rebuilding for every change done while development. Since ts-node does not directly support --env-file yet, we are using -r to require (import) ts-node/register

  • build: tsc which is part of typescript package will transpile our ts into js and place it in .dist folder as mentioned in tsconfig.json file
  • build⌚ This will run build in watch mode

On running npm run start:dev on terminal, our app will start running on port 3000. When you will navigate to localhost:3000 you will receive hello world.

Next, we will add some routes for interacting with blog and user resource. Create a folder named routes and add two files named blog.router.ts and user.router.ts

// blog.router.ts

import express, { Request, Response } from 'express';
const router = express.Router()

router.get('/', (req: Request, res: Response) => {
    res.send('List of blogs');
});

router.get('/:id', (req: Request, res: Response) => {
    res.send(`Blog: ${req.params.id} `)
});

router.post('/', (req: Request, res: Response) => {
    res.send(`Blog created`);
});

router.delete('/:id', (req: Request, res: Response) => {
    res.send(`Blog with id: ${req.params.id} deleted`);
});

export default router
Enter fullscreen mode Exit fullscreen mode
//user.router.ts

import express, { Request, Response } from 'express';
const router = express.Router()

router.get('/', (req: Request, res: Response) => {
    res.send('List of users');
});

router.get('/:id', (req: Request, res: Response) => {
    res.send(`User: ${req.params.id} `)
});

router.post('/', (req: Request, res: Response) => {
    res.send(`User created`);
});

router.delete('/:id', (req: Request, res: Response) => {
    res.send(`User with id: ${req.params.id} deleted`);
});

export default router
Enter fullscreen mode Exit fullscreen mode

Import these routes in our main index.ts file

//index.ts

import blogRouter from './routes/blog.route';
import userRouter from './routes/user.route';

app.use('/blog', blogRouter);
app.use('/user', userRouter);

Enter fullscreen mode Exit fullscreen mode

You can now access respective routes at /blog/ and /user/

Next We need to add Database for storing our data. We will use image of MySQL from docker hub and prisma for ORM for performing CRUD operations on MySQL.
We will start with prisma.

npm install prisma --save-dev
npx prisma init --datasource-provider mysql
Enter fullscreen mode Exit fullscreen mode

This will generate prisma folder in our root directory where in schema.prisma will contain database connection details and prisma models which will be used to create/update tables in our database. We will add two models in schema.prisma

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  blogs     Blog[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Blog {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}
Enter fullscreen mode Exit fullscreen mode

This model files are self-explanatory. You can check more about prisma model here: https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference
Next step is to execute prisma script to run migration which will create these model files inside our database. But before this we need to setup our mysql instance

docker pull mysql

docker run -d --name mysqldb -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mysql
Enter fullscreen mode Exit fullscreen mode

This will pull mysql image from docker hub and create container from it, we have set container name as mysqldb and root password as password. Our db will listen on port 3306

docker exec -it mysqldb sh

mysql -u root -p 
// enter "password" when prompted

create database bloggo;
Enter fullscreen mode Exit fullscreen mode

This way we can attach to our db container and execute our bash command to connect to mysql and create database named "bloggo". type exit to exit from mysql and container respectively
We will update our environment variable to our credentials in config.env file

DATABASE_URL="mysql://root:password@localhost:3306/bloggo"
Enter fullscreen mode Exit fullscreen mode

Next we will execute prisma script to run migrations, this command also generates prisma client files which comes in handy when using ORM

npx prisma migrate dev --name init
Enter fullscreen mode Exit fullscreen mode

You can now check that tables have been connected by connecting to mysql instance. Run this commands after connecting to mysql instance

use bloggo;
show tables;

describe Blog;
describe User;
Enter fullscreen mode Exit fullscreen mode

Now we can use ORM to perform CRUD operations on our database. Let's update our route files to use prisma client to run operations.

// user.route.ts

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

router.get('/', async (req: Request, res: Response) => {
    const users = await prisma.user.findMany();
    res.send(users);
});

router.get('/:id', async (req: Request, res: Response) => {
    const user = await prisma.user.findUnique({
        where: {
            id: parseInt(req.params.id),
        },
    })

    res.send(user);
});

router.post('/', async (req: Request, res: Response) => {
    const user = await prisma.user.create({
        data: req.body
    })

    res.send(user);
});

router.delete('/:id', async (req: Request, res: Response) => {
    await prisma.user.delete({
        where: {
            id: parseInt(req.params.id),
        },
    })
    res.send(`User with id: ${req.params.id} deleted`);
});
Enter fullscreen mode Exit fullscreen mode
//blog.route.ts

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();

router.get('/', async (req: Request, res: Response) => {
    const blogs = await prisma.blog.findMany();
    res.send(blogs);
});

router.get('/:id', async (req: Request, res: Response) => {
    const blog = await prisma.blog.findUnique({
        where: {
            id: parseInt(req.params.id),
        },
    })

    res.send(blog);
});

router.post('/', async (req: Request, res: Response) => {
    const createdBlog = await prisma.blog.create({ data: req.body });
    res.send(createdBlog);
});

router.delete('/:id', async (req: Request, res: Response) => {
    await prisma.blog.delete({
        where: {
            id: parseInt(req.params.id),
        },
    })
    res.send(`Blog with id: ${req.params.id} deleted`);
});
Enter fullscreen mode Exit fullscreen mode

Now, we can create user and their blog using your favorite tool like Postman.

// First we create a dummy user
curl --location 'localhost:3000/user' \
--header 'Content-Type: application/json' \
--data '{
    "email": "abc@test.com"
}'

// Then blog from userId returned from previous command. In this case, authorId 1
curl --location 'localhost:3000/blog' \
--header 'Content-Type: application/json' \
--data '{
    "title": "Title",
    "content": "Content",
    "authorId": 1
}'
Enter fullscreen mode Exit fullscreen mode

This finishes up our setup of CRUD app. We started by first creating an empty directory for our blog application and initializing it with npm to setup package.json. We added typescript support by installing few packages and generating tsconfig.json. We added few CRUD apis for Blog and User. We then added Prisma for ORM and created instance for MySQL, connected the two, ran migrations and in the end we got full functioning CRUD app.
This app is far from production like we don't have auth, request body validations, pagination, docker setup for our api and many other things. These things will be covered in later post.

💖 💪 🙅 🚩
sumitbhanushali
Sumit Bhanushali

Posted on November 25, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related