Fast backends in Node.js using PostgreSQL
In this post, I would like to talk about the open source work at e.GO Mobile.
Many projects, which are not open source themselves, such as many of our in-house projects, are based on this type of software.
Examples for this are:
- Node.js
- TypeScript
- Docker
- PostgreSQL
- and many many more
Therefore, it is consistent for the company to also dedicate itself to this topic: on the one hand, to become more well-known and to emphasize its role as a software-driven company … on the other hand, to receive feedback on the code.
Today, I would like to introduce 2 projects that are used in many of our projects:
- @egomobile/http-server - A self-written and faster alternative to Express.js
- @egomobile/orm-pg - A simple and fast ORM mapper for PostgreSQL
For this, I have set up a small GitHub repository that shows how to set up a small backend project with:
- REST API
- connection to a PostgreSQL server
- ORM mapper with POCOs and database migrations
Backend
I have built the backend in Node.js using our own module @egomobile/http-server
, which is officially available on NPM.
The API is very similar to Express.js and supports the most important things, such as:
- routing with parameters
- middlewares
With just a few lines of code, you can build an HTTP server that, unlike Express, does not have all the overhead.
To do this, you first need to install the module in your project:
npm install @egomobile/http-server --save
The following “Hello, world!” example shows that there are not many differences to Express:
import { createServer } from "@egomobile/http-server"
// npm i cors && npm i -D @types/cors
import cors from "cors"
const app = createServer()
app.get(
"/",
[cors()], // use common middlewares, like `cors`
async (request, response) => {
// request: https://egomobile.github.io/node-http-server/interfaces/IHttpRequest.html
// response: https://egomobile.github.io/node-http-server/interfaces/IHttpResponse.html
response.write("Hello, world!")
// a `response.end()` call is not needed anymore
}
)
await app.listen(8080)
console.log("Server now runs on port", app.port)
What we now have is a HTTP server, with a GET
endpoint for the route http://localhost:8080/.
PostgreSQL connection
For the connection to a PostgreSQL database, we use 2 self-written modules, which can also be found on NPM:
These provide, just like the HTTP module, a simple and fast API without much overhead and incompatible features, as is the case with TypeORM, for example.
These can be installed again using tools such as NPM or Yarn:
npm install @egomobile/orm --save
npm install @egomobile/orm-pg --save
Both provide us with functions to establish connections to databases, create migration scripts, and map data to simple class objects.
The file /src/databases/postgres/index.ts should demonstrate how we configure our projects for this purpose:
import {
createWithPostgres,
registerBigIntAsNumber
} from "@egomobile/orm-pg";
import pg from "pg"
// bigint e.g. is returned as string, so we can keep sure to have a number here
registerBigIntAsNumber({
"pgModule": pg
})
// in this folder we organize the POCOs
import getDefaultEntityConfiguration from "./entities/default"
// `createWithPostgres` is a factory function that creates
// a new PostgreSQL connection with specific configuration
// organized by connection names
export const withPostgres = createWithPostgres({
"default": {
"client": () => {
// this is the default and lets
// `pg` module load the connection settings from
// following environment variables:
//
// - PGDATABASE
// - PGHOST
// - PGPASSWORD
// - PGPORT
// - PGSSLMODE
// - PGUSER
// - PORT
return {};
},
"clientClass": pg.Client,
"entities": getDefaultEntityConfiguration,
"noDbNull": true
}
})
Migrations
In case we need to update the database structure, we simply call the script migration:create
, which creates a new migration script in the /database/migrations/scripts folder for us:
npm run migration:create MyNewMigration
An implementation could later look like this, for example:
import type { MigrationAction } from "@egomobile/orm-pg";
const logTable = "logs";
/**
* Function to UP-grade the database.
*/
export const up: MigrationAction = async (adapter, context, debug) => {
// adapter => https://egomobile.github.io/node-orm-pg/classes/PostgreSQLDataAdapter.html
// context => https://egomobile.github.io/node-orm/interfaces/IDataContext.html
// debug => https://egomobile.github.io/node-orm-pg/modules.html#DebugAction
await adapter.query(`
CREATE TABLE public.${logTable}
(
"id" bigserial NOT NULL,
"uuid" uuid DEFAULT uuid_in(md5(random()::text || random()::text)::cstring) NOT NULL,
"message" json,
"time" timestamp with time zone DEFAULT now() NOT NULL,
"type" smallint
)
WITH (
OIDS = FALSE
);
`);
};
/**
* Function to DOWN-grade the database.
*/
export const down: MigrationAction = async (adapter, context, debug) => {
// adapter => https://egomobile.github.io/node-orm-pg/classes/PostgreSQLDataAdapter.html
// context => https://egomobile.github.io/node-orm/interfaces/IDataContext.html
// debug => https://egomobile.github.io/node-orm-pg/modules.html#DebugAction
await adapter.query(`DROP TABLE public.${logTable};`);
};
In the NPM start
script, all migrations that still need to be executed are called every time the server is booting.
Start the demo project
Before you can start the demo project, you have to install Docker with docker-compose.
After this, run
docker-compose up
from the repositorie’s root directory inside a “terminal” and use tools like Postman or REST Client for Visual Studio Code to access the endpoints as defined in /src/handlers folder.
Conclusion & outlook
As you can see, it is easy to setup a stable, fast and powerful API and database support with relatively few lines of code.
We at e.GO only need a little time and are not dependent on external developers for such critical modules.
Have fun trying it out! 🎉
P.S.: In an upcoming post I will write about our controller framework we use, which is also part of @egomobile/http-server