Building a Node.js Server with TypeORM, Express.js, and PostgreSQL
Published

June 27, 2024

No Comments

Join the Conversation

“So you want to be a software engineer at Google?😂 Today, we’re diving into the fascinating world of building a Node.js server with TypeORM, Express.js, and PostgreSQL. If you’ve ever tried to set up a server before, you know it’s a bit like assembling IKEA furniture—everything looks great in the manual, but somehow you end up with extra pieces and a bookshelf that wobbles. Fear not! By the end of this guide, your server will be as sturdy as a Scandinavian design masterpiece.

But first, let’s get something out of the way: PostgreSQL is already installed on your machine. That’s right, you’re ahead of the game! If PostgreSQL were a superhero, it would be the unsung hero working quietly in the background while TypeORM and Express.js take the spotlight. So, grab your favorite beverage, sit back, and let’s turn those extra pieces into a server that’s ready to handle anything you throw at it!

Now, let’s get started with building a Node.js server with TypeORM, Express.js, and PostgreSQL—it’s going to be a fun ride!

A step-by-step-guide-to-building-a-node-js-server-with-typeorm-express-js-and-postgresql - setting up your project
A step-by-step-guide-to-building-a-node-js-server-with-typeorm-express-js-and-postgresql – setting up your project

Setting Up Your Project

Before we dive into building a Node.js server with TypeORM, Express Express.js, and PostgreSQL, we need to set up our project correctly. This involves initializing a new Node.js project and installing the required packages. Think of it as gathering all the ingredients before cooking a delicious meal. Here’s how to do it step by step:

Run `npm init`

First, open your terminal and navigate to the directory where you want to create your project. Then, run the following command:

npm init

This command initializes a new Node.js project. It will prompt you with several questions such as the project name, version, description, entry point, and more. You can press Enter to accept the default values or customize them as needed. Once complete, a `package.json` file will be created in your project directory. This file keeps track of your project’s dependencies and scripts.

Install Necessary Packages

Next, we need to install the essential packages that will power our Node.js server. Run the following commands to install them:


npm install express body-parser helmet cors xss-clean http-status-codes reflect-metadata typeorm pg tsyringe

npm install @types/cors @types/express typescript ts-node @types/node @types/nodemon nodemon tsconfig-paths --save-dev

Let’s break down these packages:

  • `express`: A fast, unopinionated, minimalist web framework for Node.js, perfect for building our server.
  • `body-parser`: Middleware to parse incoming request bodies in a middleware before your handlers, available under the `req.body` property.
  • `helmet`: Helps secure your Express apps by setting various HTTP headers.
  • `cors`: Middleware to enable Cross-Origin Resource Sharing, allowing your API to be accessed from different domains.
  • `xss-clean`: Middleware to sanitize user input and prevent cross-site scripting (XSS) attacks.
  • `http-status-codes`: Utility to work with HTTP status codes.
  • `reflect-metadata`: A dependency of TypeORM, enabling metadata reflection in TypeScript.
  • `typeorm`: A powerful ORM (Object-Relational Mapper) to work with databases using TypeScript and JavaScript.
  • `pg`: PostgreSQL client for Node.js, which TypeORM uses to connect to PostgreSQL databases.
  • `tsyringe`: A lightweight dependency injection container for TypeScript/JavaScript.
  • `@types/cors`, `@types/express`, `typescript`, `ts-node`, `@types/node`, `@types/nodemon`, `nodemon`, `tsconfig-paths`: Development dependencies that enhance our development experience, provide TypeScript types, and enable tools like `nodemon` to watch for file changes.

Understand `--save` and `--save-dev`

In npm, the `--save-dev` option (`--save-dev` or `-D`) is used when installing packages that are needed only for development purposes, not for the production build. These packages are added to the `devDependencies` section of your `package.json` file.

`--save` (or default installation without any flags):

  •   Used to install packages that are required for your application to run in production.
  • These packages are added to the `dependencies` section of `package.json`.

Example: `npm install express`

`–save-dev` (or `-D`):

  • Used to install packages that are only needed during the development of your application.
  • These packages are added to the `devDependencies` section of `package.json`.

Example: `npm install typescript --save-dev`

Initialize TypeScript Configuration

Next, run the following command to create a `tsconfig.json` file for your project:

npx tsc --init

This will create a `tsconfig.json` file that helps configure TypeScript in your project. It usually comes with a default config, but you can customize it like this:


{

  "compilerOptions": {

    "module": "commonjs",

    "noUnusedLocals": false,

    "noUnusedParameters": false,

    "experimentalDecorators": true,

    "emitDecoratorMetadata": true,

    "forceConsistentCasingInFileNames": true,

    "allowSyntheticDefaultImports": true,

    "pretty": true,

    "strictNullChecks": true,

    "sourceMap": true,

    "allowJs": true,

    "outDir": "./dist",

    "moduleResolution": "node",

    "lib": ["ES2017"],

    "target": "es2015",

    "rootDir": "./",

    "resolveJsonModule": true,

    "esModuleInterop": true,

    "skipLibCheck": true,

    "plugins": [

      {

        "transform": "typescript-transform-paths"

      },

      {

        "transform": "typescript-transform-paths",

        "afterDeclarations": true

      }

    ]

  },

  "include": [

    "src/",

    "package.json",

    "package-lock.json",

    "dump/mode.index.ts"

  ],

  "exclude": [

    "node_modules",

    ".build",

    "src//*.test.ts",

    "src//test.ts",

    "src//test.*.ts",

    "src/ec-test-utils/*.ts"

  ]

}

Creating the Server

creating the server
a-step-by-step-guide-to-building-a-node-js-server-with-typeorm-express-js-and-postgresql creating the server

Let’s create the server step by step:

Create a file called `app.ts` and add the following code:


import "reflect-metadata";

import express, { Application, NextFunction, Request, Response } from "express";

const app: Application = express();

import bodyParser from "body-parser";

import helmet from "helmet";

import cors from "cors";

import { StatusCodes } from "http-status-codes";

import xssClean from "xss-clean";

// Middleware setup

app.use(express.json());

app.use(express.urlencoded({ extended: true }));

app.use(helmet());

app.use(bodyParser.json());

const corsOptions = {

  origin: "*",

  methods: "GET,HEAD,PUT,PATCH,POST,DELETE",

  credentials: true,

  optionsSuccessStatus: 204,

};

app.use(cors(corsOptions));

app.use(xssClean());

app.disable("x-powered-by");

// Define a basic route

const apiPath = "/api/v1";

app.get(apiPath, (_: Request, res: Response) => {

  return res.status(StatusCodes.OK).json({ message: "Welcome to PRESTIGE-SERVICE", statusCode: 200, data: null });

});

export default app;

Create `index.ts`

Create another file in the `src` folder named `index.ts` and add the following code:


import app from "./app";

async function startServer() {

  try {

    const port = 3210;

    app.listen(port, () => {

      console.log(`Server started on port ${port} 🔥🔥🔥`);

    });

    process.once("SIGUSR2", function () {

      process.kill(process.pid, "SIGUSR2");

    });

    process.on("SIGINT", function () {

      // this is only called on ctrl+c, not restart

      process.kill(process.pid, "SIGINT");

    });

  } catch (err: unknown) {

    if (err instanceof Error) {

      console.error(`Database connection error: ${err.message}`);

    } else {

      console.error(`Unexpected error during startup: ${err}`);

    }

    process.exit(1);

  }

}

startServer();

Configure `package.json`

Now, remove whatever is in the `scripts` object in your `package.json` file and replace it with this for now:

...

"scripts": {

  "dev": "ts-node src/index.ts",

  "clean": "rm -rf ./dist",

  "build": "npm run clean && tsc"

}
...

You should see this in your console or terminal when you run `npm run dev`:


$ npm run dev

> node-server-boilerplate@1.0.0 dev

> ts-node src/index.ts

Server started on port 3210 🔥🔥🔥

You can test the server on your browser with [http://localhost:3210/api/v1](http://localhost:3210/api/v1) and you should see something like this:


{

  "message": "Welcome to PRESTIGE-SERVICE",

  "statusCode": 200,

  "data": null

}

Understanding the Code

understanding the code
a-step-by-step-guide-to-building-a-node-js-server-with-typeorm-express-js-and-postgresql understanding the code

Now that our server is up and running, let’s

 break down some concepts and do some cleanup.

Process in Node.js

Node.js provides a `process` global object, which is an instance of EventEmitter. It can be accessed from anywhere in your code and provides information about the current Node.js process. In our `index.ts` file, we’re using `process` to handle signals like `SIGUSR2` and `SIGINT` to gracefully shut down our server.

Why We Start the Server in a Separate File

Starting the server in a separate file (i.e., `index.ts`) helps in keeping the code organized and modular. It separates the application setup and configuration from the actual server start logic, making it easier to manage and maintain.

Why We Use Certain Packages in `app.ts`

  • `express`: The core of our application, providing the framework for building our server.
  • `bodyparser`: Parses incoming request bodies to make it easier to handle form submissions and JSON payloads.
  • `helmet`: Adds security-related HTTP headers to enhance the security of our application.
  • `cors`: Enables Cross-Origin Resource Sharing, allowing our API to be accessible from different domains.
  • `xss-clean`: Protects our application from cross-site scripting attacks by sanitizing user input.
  • `http-status-codes`: Provides a set of constants representing HTTP status codes to make our code more readable and maintainable.

Adding Error Handling

Let’s create a folder inside our `src` folder called `Middlewares` and create a file in it named `appErrors.ts`. Here’s the code:


import { Request, Response, NextFunction, RequestHandler } from "express";

import { StatusCodes } from "http-status-codes";

export const clientBadRequestError = async (err: any, _: Request, res: Response, __: NextFunction) => {

  if (err) {

    return res.status(err.status || StatusCodes.INTERNAL_SERVER_ERROR).json({

      success: false,

      message: err.message,

      status: err.status || StatusCodes.INTERNAL_SERVER_ERROR,

    });

  } else {

    return res.status(StatusCodes.BAD_REQUEST).json({

      success: false,

      message: "⚠️ Oops! It looks like something went wrong on your end. Please double-check your client-side code and try again! 💻🔍",

      status: StatusCodes.UNPROCESSABLE_ENTITY,

    });

  }

};

export const clientPathNotFoundError: RequestHandler = (req, res) => {

  return res.status(StatusCodes.NOT_FOUND).json({

    statusCode: 404,

    message: "This path exists by only by faith",

    data: null,

  });

};

When building a Node.js server, it’s crucial to handle errors gracefully and provide meaningful feedback to users. This code snippet illustrates how to implement effective error handling in an Express application, ensuring a smooth development experience and robust server functionality.

What This Code Does:

Import Statements:

First and foremost, the code begins by importing essential modules and types from the express library and the http-status-codes library. These imports are foundational when building a Node.js server because they provide the tools needed for handling HTTP requests and responses, as well as standardizing status codes.

Error Handling Function:

At the heart of this snippet is the clientBadRequestError function, designed to manage errors in your Express application. This function takes four parameters:

  • err: An error object containing details about what went wrong.
  • _: The incoming request object, not used here, hence a placeholder.
  • res: The response object used to send back a response to the client.
  • __: The next function in the middleware chain, also a placeholder in this context.

Inside the Function:

The function first checks if there is an error (if (err)). If an error is present, it sends a response with either the status code from the error or 500 (Internal Server Error) if the status isn’t specified. The response includes a JSON object with:

  • success: false, indicating the request was unsuccessful.
  • message: err.message, providing the error message.
  • status: err.status or 500, denoting the status code.

Conversely, if there is no error, the function sends a response with a 400 (Bad Request) status code. This response also includes a JSON object:

  • success: false, again indicating an unsuccessful request.
  • message, a friendly note prompting the client to check their code.
  • status: 422 (Unprocessable Entity), suggesting an issue with the client’s request.

Path Not Found Error Handling Function:

At the core of this snippet is the clientPathNotFoundError function, a middleware function designed to handle cases where a user tries to access a route that doesn’t exist. This function takes two parameters:

  • req: The request object representing the incoming HTTP request.
  • res: The response object used to send back a response to the client.

Inside the Function:

The function immediately sends a response with a 404 (Not Found) status code. The response includes a JSON object with:

  • statusCode: 404, indicating the resource was not found.
  • message, a whimsical message stating “This path exists only by faith.”
  • data: null, indicating there’s no additional data to provide.

Using Nodemon

To include `nodemon` in our scripts, modify the `scripts` object in `package.json` to:


"scripts": {

  "dev": "nodemon --delay 500ms src/index.ts",

  "clean": "rm -rf ./dist",

  "build": "npm run clean && tsc"

}

The `--delay 500ms` option introduces a 500 milliseconds delay before restarting the server after changes are detected. This can help prevent the server from restarting too frequently during rapid development changes.

Adding Error Handling to `app.ts`

Finally, let’s update `app.ts` to include our error handling methods:


import { clientBadRequestError, clientPathNotFoundError } from "./Middlewares/appErrors";

// ... Don't change the code above


app.use(clientBadRequestError);

app.use(clientPathNotFoundError);

// ... Don't change the code below



By adding app.use(clientBadRequestError) and app.use(clientPathNotFoundError), this error handling function is incorporated into the Express app as middleware. Middleware functions are pivotal when building a Node.js server as they have access to the request and response objects, and the next function in the app’s request-response cycle.

Why We Use This Code

why we use the code
a-step-by-step-guide-to-building-a-node-js-server-with-typeorm-express-js-and-postgresql why we use the code

Error Handling

This code is integral to managing errors in your application consistently. When something goes awry, it provides a structured response to the client, packed with useful information. Effective error handling is a cornerstone of building a Node.js server, ensuring stability and reliability.

User-Friendly Messages

If an error stems from the client side, the response includes a helpful message that encourages the user to check their code. This approach can significantly aid developers in debugging issues more efficiently. Friendly error messages improve the user experience, making building a Node.js server more intuitive.

Consistent Responses

Centralizing error handling in one function ensures that all errors are managed uniformly, making it easier to understand and resolve issues. Consistency in error responses is essential when building a Node.js server, as it leads to more predictable and maintainable code.

Security and Debugging

Providing detailed error messages in a controlled manner helps with debugging while preventing the exposure of sensitive server information. This balance between helpfulness and security is vital when building a Node.js server, safeguarding the application while aiding developers.

In summary, this code is a crucial component of handling errors in your Express application. By implementing consistent, user-friendly, and secure error responses, it enhances the overall stability and reliability of your Node.js server. When building a Node.js server, such practices ensure a robust and developer-friendly environment, paving the way for successful application development.

Now, test a non-existing route on our server in the browser. You should get a response like this at http://localhost:3210/api/v1/adsgfadfg


{

  "statusCode": 404,

  "message": "This path exists by only by faith",

  "data": null

}

And there you have it! You’ve successfully completed your first step in setting up a robust Node.js server using TypeORM, Express.js, and PostgreSQL. By following this guide, you’ve laid a solid foundation for building scalable and maintainable web applications. From initializing your project and configuring TypeScript to setting up essential middleware and error handling, you’ve covered a lot of ground.

In our next post, we’ll dive into configuring and initializing TypeORM and PostgreSQL, ensuring our server can effectively interact with the database. Stay tuned for more detailed tutorials that will help you master these technologies and take your server to the next level.

Remember, the journey of learning to code is filled with challenges and triumphs. Each step you take brings you closer to becoming a proficient developer. So keep experimenting, keep learning, and most importantly, keep coding!

“Code is like humor. When you have to explain it, it’s bad.” 

Happy coding, and see you in the next post! 

Leave a Reply

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