DEV Community

Grae
Grae

Posted on

Learn Express.js with me~

Today, we will be learning about Express.js using TypeORM with the operations: Create, Read, Update and Delete or CRUD.

To start off, let’s get to know Express.js or simply, Express, first shall we?

What do we know about Express?

Express.js is a popular web application framework that simplifies building reliable and scalable web applications for Node.js. It is a fast and lightweight framework used majorly for web application development and is majorly responsible for handling the backend part.


Before we get on with coding, let us first prepare what we will be needing for this project.

  • We will need to create a folder directory, and it could be anywhere on your computer.

  • Then, open your Postman and pgAdmin (PostgreSQL) applications.

pgAdmin

Within pgAdmin, create a database to use for this project. To do this, click on the drop down arrow of the Servers, then do the same with PostgreSQL 16. Right click on the Databases, select Create and then Database.

pgAdmin

Name your database according to what you will use it for, and then save.

Rename db

Postman

Then, on your Postman, create a new collection by clicking on the PLUS sign on the left and then the Blank Collection.

Postman

You can then rename the collection by clicking the three dots on the right side of the bar or once the panel has opened on the right.

Rename Collection

Once created, click on the Add a Request underneath the newly created collection. You can then rename the request once it opened on the right panel.

Postman Request

Once everything had been prepared, let us now move on to VSCode!!
(or whatever platform you use in coding)

On your VSCode, open the folder that we created earlier or simply CTRL+K then CTRL+O

VSCODE

Once the folder directory has been opened, you can go ahead and open the Terminal or simply CTRL+Shift+'

VSCode Terminal

On the Terminal, choose Git Bash

gitbash

Firstly, we will install Typeorm globally, to do that, we'll use:

$ npm install -g typeorm 
Enter fullscreen mode Exit fullscreen mode

Once that's done, we will then use:

$ typeorm init --name express_project --express --database postgres
Enter fullscreen mode Exit fullscreen mode

We use this code to automatically install and create packages.

  • typeorm init is used to initialize ORM
  • --name express_project is used to assign project name. (In this case, express_project is the assigned project name.
  • --express is used to install Express.
  • --database postgres is used to call upon the database that we will use, which is Postgres.

At this point, we can now notice the automatically generated folders right here:
vscode

  • tsconfig.json is responsible in handling typescript rules.
  • package.json contains descriptive and functional metadata about the project, such as the name, version, and dependencies.
  • .gitignore tells Git which files to ignore when committing your project to the GitHub repository.
  • src holds the .java files and stores the whole project's unminified source code.

Now, let us then redirect our Terminal to the folder that we are using which is express_project within our EXPRESS_JS.

$ cd express_project
Enter fullscreen mode Exit fullscreen mode

Once you have been redirected to the folder, it should look like this:

terminal 1

Now that we have entered our folder, let us then install Nodemon. Nodemon monitors your project directory and automatically restarts your node application when it detects any changes, making the development of the node.js applications speedy.

$ npm i nodemon
Enter fullscreen mode Exit fullscreen mode

After installing, within the root of our directory, let us then create a configuration file for Nodemon.

nodemon.json

Within nodemon.json, add this:

{
    "watch": ["src"],
    "ignore": ["src/**/*.test.ts", "node_modules"],
    "ext": "ts,mjs,js,json,graphql",
    "exec": "npm run build && node ./build/index.js",
    "legacyWatch": true
} 
Enter fullscreen mode Exit fullscreen mode
  • "watch": ["src"], watches the entirety of the src folder. It reruns/restarts the application once changes are applied.
  • "ignore": ["src/**/*.test.ts", "node_modules"], excludes the src files within the brackets from being watched.
  • "ext": "ts,mjs,js,json,graphql", are the extensions that will be checked during runtime.
  • "exec": "npm run build && node ./build/index.js", are the commands that will be executed once file changes are made.
  • "legacyWatch": true enables an older, less efficient file-watching mechanism. This option is used when you encounter issues with the default file watcher.

Afterwards, let us then install rimraf. Rimraf command is an executable that is used to clean the installed node packages in a node based project. It basically executes the rm -rf command on the node_modules directory, but is a faster alternative. You can install rimraf by using the below code on your terminal.

$ npm install rimraf
Enter fullscreen mode Exit fullscreen mode

Moving on, we can now proceed with setting up scripts within our package.json. This is how it looks like initially.

package.json

On the bottom lines (21-25) we can find the scripts that we need to edit.

And since we have installed Nodemon, TypeORM and rimraf, they should be included in the scripts as well for them to test, build, and streamline the needed commands to work with a module.

"scripts": {
      "dev": "nodemon",
      "build": "rimraf build && concurrently \"tsc\"",
      "start": "npm build && nodemon src/index.ts",
      "typeorm": "typeorm-ts-node-commonjs"
   }
}
Enter fullscreen mode Exit fullscreen mode
  • "dev": "nodemon", pertains to the configuration for the recently installed nodemon namely nodemon.json.
  • "build": "rimraf build && concurrently \"tsc\"", deletes old build folder and then creates a new one
  • "start": "npm build && nodemon src/index.ts", this will serve as the command that will be run and restarted by nodemon when a file changes.
  • "typeorm": "typeorm-ts-node-commonjs" is used to generate migrations.

Upon saving the package.json file, there might be a time where it will fail, and if it does, just choose to Overwrite it and that should do the trick.

Once the Script has been successfully set up, let us then move on to the Data Source (assuming that the database has been prepared).

Simply go to data-source.ts, which you can find in your src folder.

This is where we will be able to connect our database to the express application.

data-source.ts

Initially, this is how the data-source.ts looks like however, with a set-up like this, sensitive information might be displayed or uploaded.

To prevent this, we will use .env

To set up, we will need to create a .env file into the root of our directory.

dotenv

The information included here are the ones that we will not allow to be uploaded into our github. Namely, host, port, username, password, database, synchronization and logging from data-source.ts.

dotenv info

  • POSTGRES_DB = project_exp pertains to the name of the database used
  • POSTGRES_PORT = 5432, pertains to the port number which is also the default.
  • POSTGRES_PASSWORD = admin pertains to your postgres password
  • POSTGRES_USERNAME = postgres pertains to your postgres username
  • POSTGRES_HOST = localhost pertains to the hose
  • POSTGRES_SYNC = true, pertains to the synchronization of data. True pertains to the permission to synchronize data and any changes made within the entity will then be automatically saved within the tables in the database.

Any term/word can be used to name these variables so long as the .env file is imported into the data_source.ts file. To import .env, the below code shall be included within data-source.ts.

import "dotenv/config";
Enter fullscreen mode Exit fullscreen mode

this is now how our data-source.ts file should look like:

dtsrc

Notice that there are some changes on the data-source.ts file. On the photo above, a property called process.env has been added. process.env is a property that returns an object containing the user environment.

We include *process.env* and call upon the terms/names used from our .env file.

import "reflect-metadata"
import { DataSource } from "typeorm"
import "dotenv/config";

export const AppDataSource = new DataSource({
    type: "postgres",
    host: process.env.POSTGRES_HOST,
    port: Number(process.env.POSTGRES_PORT),
    username: process.env.POSTGRES_USERNAME,
    password: process.env.POSTGRES_PASSWORD,
    database: process.env.POSTGRES_DB,
    synchronize: !!process.env.POSTGRES_SYNC,
    logging: false,
    entities: ["build/entity/*.js", "build/entity/**/*.js"],
    migrations: [], 
    subscribers: [], 

});
Enter fullscreen mode Exit fullscreen mode

*entities: ["build/entity/*.js", "build/entity/**/*.js"*]

this just entails that every .js files within the build folder will be saved on the database as well as every changes made within the entities.

Before we run, be sure to install .env file into your terminal as well using:

$ npm install dotenv -D
Enter fullscreen mode Exit fullscreen mode

If error occurs, kindly check if the .env file is saved within the root directory.

Moving on, once every entities have been saved, we will then proceed to index.ts.

Initially, this is how the generated index.ts file will look like:

import * as express from "express"
import * as bodyParser from "body-parser"
import { Request, Response } from "express"
import { AppDataSource } from "./data-source"
import { Routes } from "./routes"
import { User } from "./entity/User"

AppDataSource.initialize().then(async () => {

    // create express app
    const app = express()
    app.use(bodyParser.json())

    // register express routes from defined application routes
    Routes.forEach(route => {
        (app as any)[route.method](route.route, (req: Request, res: Response, next: Function) => {
            const result = (new (route.controller as any))[route.action](req, res, next)
            if (result instanceof Promise) {
                result.then(result => result !== null && result !== undefined ? res.send(result) : undefined)

            } else if (result !== null && result !== undefined) {
                res.json(result)
            }
        })
    })

    // setup express app here
    // ...

    // start express server
    app.listen(3000)

    // insert new users for test
    await AppDataSource.manager.save(
        AppDataSource.manager.create(User, {
            firstName: "Timber",
            lastName: "Saw",
            age: 27
        })
    )
    await AppDataSource.manager.save(
        AppDataSource.manager.create(User, {
            firstName: "Phantom",
            lastName: "Assassin",
            age: 24
        })
    )
    console.log("Express server has started on port 3000. Open http://localhost:3000/users to see results")

}).catch(error => console.log(error))

Enter fullscreen mode Exit fullscreen mode

Since we will be using repositories, let's just simplify our index.ts into this:
index.ts

  • AppDataSource.initialize() initializes the AppDataSource along with the server. Simply put, once the server starts, the database will also start along with it. AppDataSource (data-source.ts) connects our files into our database.
  • const app = express(); creates express application.
  • app.use(bodyParser.json()); is a middleware that is used to prevent errors with .json requests and responses.
  • app.listen(3000); entails to which port you will use into your app.
  • console.log is used to output a message into the terminal for us to be able to see if the project is working.

Once index.ts has been prepared, we will then be creating folders.

folders

From the package that we installed earlier, the following folders shown in the photo are the folders that were generated. But since we will be arranging these items in a corporate standard, other files/folders should be removed.

The migration folder which is located within the src folder along with the routes.ts file should be removed. This is because there will be folder/files that we will include separately later during this lesson.

migration

routesdel

Now, within the src folder, let us then add folders to which we will name: routes, services and repositories individually.

routes, services and repositories

In the repositories folder, we will need to create a file where all the entities that we will connect and declare within the database will be added. Let's name it UserRepository.ts.

user repo

Then, within the UserRepositories.ts file, encode this:

import { AppDataSource } from "../data-source";
import { User } from "../entity/User";
import { UserService } from "../service/userService";

export const userRepository = new UserService(
    AppDataSource.getRepository(User)
);
Enter fullscreen mode Exit fullscreen mode
  • export const userRepository = new UserService( The export statement allows developers to make parts of their modules available to other modules.
  • AppDataSource.getRepository(User) this is used to call upon the data source within the controller.

Then, within the services folder, let us create a file and name it UserService.ts

UserService

Within UserService.ts, this is where we will declare methods that will handle all of the so called business logic that will happen in this project.

Let us then encode this:

import { Repository} from "typeorm";
import { User } from "../entity/User";

export class UserService {
    constructor(private readonly userRepository: Repository<User>){}
    async findAll(){
        const users = await this.userRepository.find();
        return users;
    }
Enter fullscreen mode Exit fullscreen mode
  • export class UserService we export the UserService class for it to be accessible to other modules in this project.
  • constructor(private readonly userRepository: Repository<User>) we put constructor within the UserService to prevent it from constantly initializing the repository.
  • async findAll() Async simply allows us to write promises-based code as if it was synchronous and it checks that we are not breaking the execution thread.
  • const users = await this.UserRepository.find(); the await function is interconnected with async wherein await prevents responses to be made within the UserRepository so long as the promise has not been fulfilled yet.
  • return users;

With the use of the codes above, our UserRepository.ts is now connected to our database.

After this, we can now proceed to our UserController.

This is the generated UserController.ts:

import { AppDataSource } from "../data-source"
import { NextFunction, Request, Response } from "express"
import { User } from "../entity/User"

export class UserController {

    private userRepository = AppDataSource.getRepository(User)

    async all(request: Request, response: Response, next: NextFunction) {
        return this.userRepository.find()
    }

    async one(request: Request, response: Response, next: NextFunction) {
        const id = parseInt(request.params.id)


        const user = await this.userRepository.findOne({
            where: { id }
        })

        if (!user) {
            return "unregistered user"
        }
        return user
    }

    async save(request: Request, response: Response, next: NextFunction) {
        const { firstName, lastName, age } = request.body;

        const user = Object.assign(new User(), {
            firstName,
            lastName,
            age
        })

        return this.userRepository.save(user)
    }

    async remove(request: Request, response: Response, next: NextFunction) {
        const id = parseInt(request.params.id)

        let userToRemove = await this.userRepository.findOneBy({ id })

        if (!userToRemove) {
            return "this user not exist"
        }

        await this.userRepository.remove(userToRemove)

        return "user has been removed"
    }

}
Enter fullscreen mode Exit fullscreen mode

Clean it up into this:

ucontroller

NOTE:
Within the UserRepository.ts, the UserRepository initializes repositories and instantiates the UserService.

Wherein, the UserService within the UserService.ts was injected upon by the dependency: (private readonly userRepository: Repository<User>) (or what they call dependency injection).

And since the dependency has been injected, we use this.UserRepository.find(); instead to call upon the UserRepository.

Then, within the UserController.ts, instead of calling upon the UserRepository in another ways, we use UserRepository.findAll() instead.

You can find findAll() from the UserService.ts.

Controller accepts/receives user requests to which it will then transfer into service. All business logics happen within the service folder.

Moving on, let us then proceed to create a .ts file within the routes folder.

routes.ts

In the new UserRouter.ts file, we will then include this:

import { Router } from 'express'
import { UserController } from '../controller/UserController'

const userRouter = Router()

userRouter.get("/users", UserController.all);

export default userRouter;
Enter fullscreen mode Exit fullscreen mode

UserRouter.get("/users", UserController.all);

  • *.get* seeing as the command that was used from our UserService is findAll, we will be using get as the HTTP verb.
  • "/users" is the specified routing where the HTTP verb will proceed.
  • UserController.all to connect the routes into the controller.

Having these codes encoded will not make our program functional just yet, to proceed with that, let us then go to our index.ts file first and include this:

index upd1
see the highlighted part on line 11

Using this, we will be able to view the entirety of the data we have which is its limitation. What if we wanted to just view a single item from a single ID?

To be able to do that, let us go back into our UserService.ts and use the below code right underneath our *findAll* command.

async findOne(id: number){
        const users = await this.userRepository.findOne({ where: { id } });
        return users;
    }
Enter fullscreen mode Exit fullscreen mode

Replacing findAll with findOne will limit our scope specifically into the ID which is the primary key.

Right after setting up our UserService.ts, we can then again proceed into our UserController to connect the latter.

static async findOne(request:Request, response:Response){
        const id = Number(request.params.id);
        const data = await userRepository.findOne(id)
        return response.send(data);
    }
Enter fullscreen mode Exit fullscreen mode
  • static async findOne(request:Request, response:Response) Copying and pasting the first set of codes we used from our Get, we will just replace the findAll with findOne.
  • const id = Number(request.params.id); this part dictates which ID we wanted to fetch from our data.

Of course, right after connecting UserService.ts and UserController.ts, we will then need to connect those with UserRouter.ts.

UserRouter

Using: userRouter.get("/users/:id", UserController.findOne);, take note of the added parameter :id which we will then access into the controller.

What we just concluded was the HTTP verb GET which also means Read or to view the fetched data.

After that, we can now proceed to the remaining HTTP verbs such as (Get, Post, Put, Patch...)

The next HTTP verb we will be tackling is the POST verb or Create.

Let us then go back to UserServices.ts and encode this:

async createUser(newuser: User){
        const user = this.userRepository.create(newuser)
        await this.userRepository.save(user);
        return user;
    }
Enter fullscreen mode Exit fullscreen mode
  • async createUser similar to what was stated earlier, async function allows functions to be defined in a sequential, synchronous manner and createUser then deals with the creation of items within the User.
  • (newuser: User) is a parameter; Parameter is a variable used in a function to refer to one of the pieces of data provided as input to the function.
  • const user = this.userRepository.create(newuser) Creates a new instance of User.
  • await this.userRepository.save(user); await is used to unwrap promises by passing a Promise as the expression.
  • return user; return statement ends the execution of a function, and returns control to the calling function.

Afterwards, we will then proceed into our UserController.ts file and connect it with UserService.ts; to connect them together, we will use:

static async create(request:Request, response:Response){
        const data = await userRepository.createUser(request.body);
        return response.status(201).send(data);
    }
Enter fullscreen mode Exit fullscreen mode

We use the status code 201 as it shows that a data has been created.

And then of course, the next step is to connect the two to the UserRouter.ts, where we will just add userRouter.post("/users", UserController.create); into the previous codes we encoded beforehand.

We use Post as our HTTP verb because we want to be able to Create items.

post router

The logic is fairly similar, if not the same, to the previous steps and codes we have done and encoded before.

The data we are sending can be found in our User.ts file within our entity folder.

entity

The firstName, lastName and age, which can be interchangable depending on what use you need it to. Furthermore, ID does not need to be sent seeing as it is a primary key and is autoincremented.

Do make sure that the data that will be encoded in the Postman is the same as the data in the entity, including the format and casing.

By this point, we are now able to view and create data. To test the waters, let us then open our terminal, and run the codes.

$ npm run dev
Enter fullscreen mode Exit fullscreen mode

Once successful, the bottom lines of the terminal will show this:

1strun

Go ahead and copy the highlighted part and paste it into your Postman using the request we have created at the beginning of this lesson.

This is what your Postman would look like:

postman1

Firstly, copying and pasting the highlighted parts on your terminal will not show the results of the project. We will need to add public into the link first: http://localhost:3000/public/users

If we try to view our data using GET, this is what Postman will show:

Postman 3

See the open and closed brackets '[]' on the console at the bottom part. This is shown because no data has been encoded into our database just yet.

Similarly, pgAdmin will show an empty output due to the same reason.

pgadmin

To be able to successfully GET data, what we will be doing first is to create data using the HTTP verb POST.

To do that, choose POST on the dropdown menu on the left most part, then look for Body and choose raw and lastly JSON on the right most as shown on the photo below.

postman 2

On the panel right below, we can now encode our data basing on the entities that we have from our User.ts file within our entity folder.

data entry

{
    "firstName": " ",
    "lastName": " ",
    "age": 
}
Enter fullscreen mode Exit fullscreen mode

Using this format, we can now add our data by clicking on SEND.

postman POST

The panel at the bottom shows the contents of our database. Note that the ID has already been generated. (Normally, the ID starts with #1 but since I have deleted the previous contents, mine started with #12 instead.)

We can then view the contents of our database using GET. To do that, simply go to the left most part beside the URL, choose GET from the dropdown menu and then send.

To individually view our data, we can call on their ID's by simply adding */id number* at the end of the url link.

Here's an example:

indiv get

after clicking send, Postman will then show this result:

indiv get result

The bottom panel then shows the data with the ID number 12.

Moving on to the next HTTP verb, we will be learning PUT & PATCH which means to Update.

As usual, let us first open our UserService.ts and encode this:

    async updateUser(id: number, data: Partial<User>) {
        const user = await this.userRepository.findOne({ where: { id } });
        if (user) {
            this.userRepository.merge(user, data);
            await this.userRepository.save(user);
            return user;
        } else {
            return { message: "User not found"};
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • async updateUser(id: number, data: Partial<User>): similar to fetching specific data, to update, we will also need an identifyer which is the primary key: id:.
  • const user = await this.userRepository.findOne({ where: { id } });: we use findOne to identify the user that needs updating.
  • if (user) {: if statement refers to a condition that will be answered by the else statement.
  • this.userRepository.merge(user, data); : this part merges the original data with the updated one.
  • await this.userRepository.save(user); : saves the newly updated data.

Afterwards. we will then proceed once again to UserController.ts.

static async update(request: Request, response: Response) {
        const id = Number(request.params.id);
        const data = await userRepository.updateUser(id, request.body);
        return response.send(data);
    }
Enter fullscreen mode Exit fullscreen mode
  • const id = Number(request.params.id); similarly to what was included within UserService.ts, this parameter is needed to fetch the id that sent the request.

Next, let us then proceed to UserRouter.ts.
PUT ROUTER

By this, we can now update our data in our database.

postman upd

Within Postman, we can update data using the HTTP verb PUT and indicating the ID number right at the end of the url link.

{
    "firstName": " ",
    "lastName": " ",
    "age": 
}
Enter fullscreen mode Exit fullscreen mode

To update, similar with POST, we will just need to encode the above code. Of course, updating data is not limited to all of the entities included within our User.ts file, we can update even one entity or two depending on what was needed.

Here is an example:

PUT EXAMPLE

Above is ID #12's original data.

I will then be updating ID #12's data into this:

PUT EX 2

Here is the result shown after submitting the request:

PUT RESULT

ID #12's last name and age have been updated as per request using PUT.

By this, we can now move on to the final part which is Delete.

Within UserService.ts, we will use:

async delete(id:number){
        const user = await this.userRepository.findOne({ where: { id } });
        if(user){
            await this.userRepository.remove(user);
            return { message: "User Successfully Removed" };
        } else {
            return { message: "User not found"};
        }
    }
Enter fullscreen mode Exit fullscreen mode
  • return { message: "User Successfully Removed" }; return message is optional but it helps to indicate if the request has been fulfilled.

Then, in our UserController.ts, we will then use:

static async delete(request: Request, response: Response){
        const id = Number(request.params.id);
        const data = await userRepository.delete(id);
        return response.send(data);
    }
Enter fullscreen mode Exit fullscreen mode

By copying and pasting the codes on the previous verbs that are quite similar to how the Delete function works, we can now move to UserRouter.ts.

router delete ey

After saving, we can now proceed to Postman~

DELETE POSTMAN

Simply choose DELETE on the drop down on the left most part AND adding the ID number at the end of the url will result to this:

DELETE


With this, we have learned how to operate CRUD using Express TypeORM.

Top comments (0)