Create a nodejs backend app that use Express with Typescript

I am going to set up a nodejs backend app that use express with typescript. In other words, create a REST API in nodejs that uses TypeScript with the help of the express framework. For the database I am going to go with MySQL. With a backend like this you can easily have axios or similar in your frontend app to communicate with the backend application. This is a simple tutorial on how to do this, very similar to my other post about Creating a simple Angular 10 App with Nodejs Back-End using MySQL although this time, we are using Typescript for express which is little bit different.

For reference, I am using Visual Code and nodejs version 12.14.0. Always a good idea to keep nodejs updated or otherwise it can be tricky trying to use various packages and modules or even when deploying to Google Cloud for example. Download or update nodejs if you don’t have it at nodejs.org.

Create a new project
Alright so let us get started by creating a new project that uses express with typescript from scratch. First create a folder that the project is going to be in, I will call my project FileIdeaNews since this going to be just a plain app where you can write news.

Open up your folder in Visual Code and then open up your terminal and type:

npx express-generator-typescript "Backend"

When we run this command we get the complete skeleton structure of express with typescript all ready for us to continue working on in a new folder we called Backend! That is pretty powerful and a simple way to get started quick. If you goal is to just set up the express with typescript you are already done here, but if you want to see how you can use the express framework with MySQL, tag along.

We should then get a folder with a bunch of things in it.

The console from Visual Code

Then use the cd command to go into the Backend folder in the terminal.

To be sure everything we need is installed run npm install:

npm i

As a bonus we got nodemon applied to our nodejs project as well. So let us try out our back-end app by running nodemon like this in the terminal at the same location:

nodemon

Let us take a look at the result by visiting localhost:3000.

The code we have from the generation

There we go! We even have some UI to play around with as well. We can add and remove users, everything here is handled in our file under daos/MockDb.json which we read, write and delete JSON data from.

To make things more simple and try things out in our own way let us not focus on these daos structure that is already made. Since we want to use MySQL we want to do things little bit different and set up our own route we work with. First off

Create a new route
Under the routes folder create a new file called Posts.ts similar to how it is made for Users.ts. In our route file we will then create a getAllPosts method. It will just return an empty array for now:

import { Request, Response } from 'express';
import { paramMissingError } from '@shared/constants';
import { StatusCodes } from 'http-status-codes';
const { BAD_REQUEST, CREATED, OK } = StatusCodes;

export async function getAllPosts(req: Request, res: Response) {
    return res.status(OK).json([]);
}

You should be able to import the function by hovering the getAllPosts to import getAllPosts from our new ./Posts.ts file. Let us proceed to make use of this new method and insert it into a new route.

There is a lot of thing already made, but what is the fun in that? Let us create our own “posts” and remove all current routes from the index.ts file and add our own like this and rename userRouter to postRouter like this:

import { Router } from 'express';
import { getAllPosts } from './Posts';

// Posts-route
const postsRouter = Router();
postsRouter.get('/all', getAllPosts);

// Export the base-router
const baseRouter = Router();
baseRouter.use('/posts', postsRouter);
export default baseRouter;

We will also remove or comment out the front-end things we got inside the Server.ts file. We don’t want to handle any front-end things so let us just get rid of it. This is code we don’t want to use for our API:

// const viewsDir = path.join(__dirname, 'views');
// app.set('views', viewsDir);
// const staticDir = path.join(__dirname, 'public');
// app.use(express.static(staticDir));
// app.get('*', (req: Request, res: Response) => {
//     res.sendFile('index.html', {root: viewsDir});
// });

Okay so now we are now using /posts for all the our routes, and we already defined one route which is to /all. If we take a look at the Server.ts file we can see that our baseRouter are passed onto app.use which means that all our routes are under the/api/ URL. If we go to localhost:3000/api/posts/all we will then run the method getAllPosts and see the empty array from our newly created method.

Result by visiting our new all route

So we just made our first API method ready to be used under api/posts/all! We don’t have any data though so it isn’t that interesting to just get “[]” so let us add the possibility to get data through MySQL. To keep it clean and structured we will create a new typescript file which will work as the service that hold all the methods for communicating with our MySQL database. First of all, let us see that we actually can work with MySQL.

Set up a simple MySQL database

For us to store data we need a MySQL database. If you don’t have MySQL installed on your computer. fear not, what I usually do is install XAMPP which comes with apache and MySQL. What is good is that when getting MySQL you also get the phpmyadmin where you can easily manage and create databases. So that is what we will do, install XAMPP if you haven’t. Click the Admin button on XAMPP when having Apache and MySQL activated (they should be green).

Default user and password when installing phpmyadmin on your local computer is this:
host: localhost
user: root
password: (empty)

Create a new database or use one you already have. Mine will be called FileIdeaNews. Proceed to create a new table called posts.

Creating a new table in phpmyadmin

I’ll create these columns for my posts table.

  • Id. int (11) datatype. AI enabled, meaning, it will be auto incremented by 1 each new row)
  • title. varchar(200) just a title with not too long character limit.
  • content TEXT. This datatype means we can have lots of characters in it, 65,535 characters to be exact.
  • date. TIMESTAMP. We don’t need to do anything, we simply let MySQL set the current date for us.

Create a typescript class for the MySQL rows

Since we are working with TypeScript it would be foolish not to use classes. So let us create a class that will be representing the objects that we will pass onto the mysql(2) query methods. Create a new folder called models under the src folder and in it, create post.model.ts. In this file create the columns we want to be working with.

export class Post {
    id: number = 0;
    title: string = "";
    content: string = "";
    date: string = "";
}

Now all we need is a connection to the database where we can work with the data! First, add a row in to the posts table so we can actually retrieve something from it. I just added a row with some random text (see the add button in red circle). Also, I wasn’t aware that my phpmyadmin was not in English, sorry about that!

From phpmyadmin showing you the add row button

Create a service for handling database queries

Let us create a new folder called services under src and in it a file called db.ts. Not going to spend a bunch of time doing everything perfect here, just want something working fast and simple. So what exactly do we need? We need a connection to a MySQL database and a simple query to read from our table. We will also use our new post class. Now this class is going to be named databaseService. I was thinking if it should be named databaseController or service, but I ended up with databaseService. Nonetheless, this is going to be where all the database queries are being executed. Of course, you might want to move the database config and password to a different place and handle these variables depending on what enviroment the app is running on, but this is just a local dev test so no need to worry about anything!

Be in your backend project folder and install the mysql2 npm package to install mysql2.

npm i mysql2

Protip (optional): You can also download typings for mysql2 like this (see this github page for information)

typings install --save mysql2

Pretty nice to use if you want the whole intellisense in Visual Studio, but then you have to write the code little bit different which is for another time so no need to download.

In the db.ts file we are going to to create a new abstract class which means we don’t need to create an object that initizes the class each time we want to do something here. We can access the class from anywhere and call its methods in it.

import logger from "@shared/Logger";
import { Post } from "src/models/post.model";
const mysql = require('mysql2');

export abstract class databaseService {

  private static sqlConfig = {
    host: 'localhost',
    user: 'root',
    password: '',
    database: 'fileideanews'
  };

  public static async getAllPosts(params: string) {
    var connection = mysql.createConnection(this.sqlConfig);

    connection.query('SELECT * FROM posts', (error: any, results: Post[], fields: any) => {
      if (error)
        logger.err(error);
      else {
        console.log(results);
      }
    });
  }
}

We also added a static async method getAllPosts which will query the database with the configuration defined in the variable sqlConfig above. It also takes in a params parameter, we don’t do anything with it yet but it can come handy in the future if you want to do a WHERE statement of some kind. Take note that we now are using TypeScript for the MySQL result rows by defining what type it is we have as result.

Try out the MySQL databaseService

Now to try make a query to our database and posts table with the help of our new method we can easily access it by going to the Posts.ts file under the routes folder. Here we call upon the getAllPosts method like this:

import { Request, Response } from 'express';
import { paramMissingError } from '@shared/constants';
import { StatusCodes } from 'http-status-codes';
import { databaseService } from 'src/services/db';
const { BAD_REQUEST, CREATED, OK } = StatusCodes;

export async function getAllPosts(req: Request, res: Response) {
    databaseService.getAllPosts("");
    return res.status(OK).json([]);
}

Let us go to http://localhost:3000/api/posts/all and check out console because we did in the method have a console.log print of our results. We should at least get 1 row we added before.

From the Visual Code console

Nice we got a result from the database when we visit our API method! However, it is in the console and not our website, how do we fix that? To accomplish that we need to start think about handling the async calls for the MySQL.

Work with async calls for MySQL

Since we want the data to come back to us and print it out to the route when the data is loaded from our database we will work with promises (to avoid getting stuck in the callback hell). Luckily there is already people who thought about how to handle the async calls for MySQL so we don’t need to reinvent the wheel. With the promise library bluebird we just need to make some small adjustments. Let us add bluebird and replace mysql with mysql2/promise in the top of our db.ts file:

// get the client
const mysql = require('mysql2/promise');
 
// get the promise implementation, we will use bluebird
const bluebird = require('bluebird');

And install bluebird:

npm i bluebird

We are also going to throw the bluebird into our createConnection so that it will handle the Promise. Since we no longer work with callbacks we will change our query to run execute without any parameters than the actual query instead. We will also have to add the keyword await added to our calls.

import logger from "@shared/Logger";
import { Post } from "src/models/post.model";
// get the client
const mysql = require('mysql2/promise');

// get the promise implementation, we will use bluebird
const bluebird = require('bluebird');

export abstract class databaseService {

  private static sqlConfig = {
    host: 'localhost',
    user: 'root',
    password: '',
    database: 'fileideanews'
  };

  public static async getAllPosts(params: string): Promise<Post[]> {
    var connection = await mysql.createConnection({
      ...this.sqlConfig,
      Promise: bluebird
    });

    let [rows, fields] = await connection.execute('SELECT * FROM posts');
    let Posts = rows.map((r: any) => {
      return <Post>r;
    })
    return Posts;
  }
}

I also wrote a short little mapper just below the execute which basically does nothing except casting each array element we got back from our query as a Post type, showing you the possibilities of what you can do with TypeScript.

Back in our Posts.ts file we will now use the await keyword to wait for our Promise to come back with all the data like this:

import { Request, Response } from 'express';
import { paramMissingError } from '@shared/constants';
import { StatusCodes } from 'http-status-codes';
import { databaseService } from 'src/services/db';
import { Post } from 'src/models/post.model';
const { BAD_REQUEST, CREATED, OK } = StatusCodes;

export async function getAllPosts(req: Request, res: Response) {
    let posts: Post[] = await databaseService.getAllPosts("");
    return res.status(OK).json(posts);
}

Let us visit our route again at http://localhost:3000/api/posts/all

And we got data back from our database! We are now working with promises!

Add a delete method

Let us create a delete method. First of all, let us delete the contents inside Backend\src\@types\express\index.d.ts since we are too lazy to define our own class here. You can go ahead and do it but it I will skip this since the tutorial is already too long. Deleting this will mean that we won’t change the Request body property of the express module.

In our db.ts file add a deletePost method:

  public static async deletePost(id: number): Promise<number> {
    var connection = await mysql.createConnection({
      ...this.sqlConfig,
      Promise: bluebird
    });

    let [rows] = await connection.execute(`DELETE FROM posts WHERE id = ${id}`);
    console.log(rows.affectedRows)
    return rows.affectedRows;
  }

In the Posts.ts file add a deletePost method:

export async function deletePost(req: Request, res: Response) {
    let id: number = 0;
    if (req.params.id)
        id = parseInt(req.params.id);
    let numDelete = await databaseService.deletePost(id);
    return res.status(OK).json(numDelete);
}

And finally add the route to the index.ts under routes:

postsRouter.get('/delete/:id', deletePost);

We will delete any post from the ID we get. And we will return how many rows which were effected, so if there is a matching ID it should return 1, otherwise 0.

Visit http://localhost:3000/api/posts/delete/1 and see if your post with id 1 is deleted. It should print out 1 if that is the case.

Add post method

Now in a similar fashion let us add a add method. Add this to db.ts:

public static async addPost(post: Post): Promise<Post> {
    var connection = await mysql.createConnection({
      ...this.sqlConfig,
      Promise: bluebird
    });

    let [rows] = await connection.execute(`
    INSERT INTO posts
    (title, content)
    VALUES ('${post.title}', '${post.content}')
    `);
    console.log(rows.affectedRows)
    return rows.affectedRows;
  }

Add this to your Posts.ts:

export async function addPost(req: Request, res: Response) {
    let post = <Post>req.body;
    let numAdded = await databaseService.addPost(post);
    return res.status(OK).json(numAdded);
}

Add to your route index.ts file:

postsRouter.post('/add/', addPost);

Use postman for using the REST API

So since we are sending post requests it is easier to try out our addPost method easier with postman. If you don’t have it install it here. Add the URL to the postman application and then switch to JSON format and enter both title and content there as shown in my picture below:

View in postman sending request to our API

To get JSON click on the raw radio button under Body then select JSON to the right in that menu. We can then click Send. Result is that we can another row in the database (and 1 printed back as effected rows)! We can see that we got knew data if we go to: http://localhost:3000/api/posts/all/.

And there we go, we have a completely fresh REST API running in nodejs. It is up to you to make the code more secure, structured and better in all kind of ways! There is tons of things to be done.

What is so good about Express with Typescript?

By using Express with TypeScript we can have a much more clearer way of coding by the help of the intellisense of our own code by having classes and methods that tells us what and types we are working with. It is the prefered choice!

Leave a comment

Your email address will not be published. Required fields are marked *