Hello everyone!
In this first post we talk about how to develop a basic backend using Node.js and Typescript as main developer language, first of all we need to details the basics aspects of the Typescript and why is very interesting to use it as default language in your application.
Throughout in this article we can build together basic backend with routes, controllers and responses, in a next post we should continue to use this application to apply unit tests using Jest and you will learn how increase the coverage and how to use it with the Typescript.
Now let's go to discovery the benefits to develop using Typescript!
What is a Typescript?
The Typescript language is a typed superset of Javascript and allow us to create more cohesive and clean code, types errors can be discovered in development time, helping the team to understanding what kind of values need to pass in function's params that have no much knowledge about.
Let's take a little example in Javascript code and your equivalent in Typescript code:
function sum(num1, num2) {
return num1 + num2;
}
Above we have a Javascript code that sum two values and returns the result, this is a simple function that can show to us the problem when you not working with typed language, let's take a call with this function with two different ways:
// Returns 4
sum(2, 2)
// Returns '22'
sum('2', 2)
When we call the sum function and the two parameters is numbers, the response will be 4, however, when some of this two parameters is a string, the Javascript will interpret that is a concat of two string and will response '22'.
This simple example show to us that non-typed language can be harder to understand by people that don't know how the code works. Because this problem, the Microsoft developed the open source language Typescript to mitigate things like that.
function sum(num1: number, num2: number) {
return num1 + num2;
}
Above is the same code that was developed in Javascript, but with parameters types, all developers can now see the types and input the correct value to the function, another values will be generate an error.
// Returns 4
sum(2, 2)
// Error in development time
sum('2', 2)
The second call is not more valid to use the sum function, an error is generated and the developer will know that need to put two numbers, not strings.
Another good aspect to use Typescript is that you can use new features available in newest ECMAScript specification and have no problem to execute in old browsers or old Node.js versions, because the Typescript compiler it will transform all of your code to ECMAScript 5 specification.
Building a backend using Typescript
Now we go to build our basic backend using Typescript as the main language, the image below show us how the solution will stay:
Where:
classes - Is a folder that contains all generic classes that our application going to use.
controllers - Is a folder that contains all controllers of our application.
routes - Is a folder that contains all routes that we should define.
services - Is a folder that contains the integrations with another systems, like request for some external APIs.
To build our application we need some packages to allow us to start the app, the package.json below contains all necessary references that we the need to install before start the configuration:
{
"name": "typescript-node-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "npm run build && node dist/index.js",
"build": "gulp scripts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.7.7",
"@types/body-parser": "1.17.1",
"@types/debug": "4.1.5",
"@types/express": "4.17.2",
"@types/morgan": "1.7.37",
"@types/node": "13.1.4",
"gulp": "4.0.2",
"gulp-babel": "8.0.0",
"gulp-typescript": "5.0.1",
"typescript": "3.7.4"
},
"dependencies": {
"body-parser": "1.19.0",
"debug": "4.1.1",
"express": "4.17.1",
"morgan": "1.9.1"
}
}
We should write our codes using Typescript, but the Node.js core need a javascript file to interpret the content and execute all commands, firstly we going to create and configure the Gulp file to transcript our Typescript to Javascript.
Below we have the basic configuration file, we need to create the gulpfile.js in the root of the application:
const gulp = require('gulp');
const babel = require('gulp-babel');
const ts = require('gulp-typescript');
const JSON_FILES = ['src/*.json', 'src/**/*.json'];
// Indicates to gulp the typescript configuration
const tsProject = ts.createProject('tsconfig.json');
const scripts = () => {
return tsProject.src()
.pipe(tsProject()).js
.pipe(babel())
.pipe(gulp.dest('dist'));
};
const watch = () => {
gulp.watch('src/**/*.ts', scripts);
};
const assets = () => {
return gulp.src(JSON_FILES).pipe(gulp.dest('dist'));
};
exports.scripts = scripts;
exports.watch = watch;
exports.assets = assets;
const build = gulp.series(gulp.parallel(scripts));
gulp.task('build', build);
gulp.task('default', build);
In this file we have some configurations, like where files will be transcripted and where will stay the javascript files created by this transcript.
The next step is define the tsconfig.json, this file contains the compile options to transcript yours Typescripts code to Javascript code, below we have the example of how this file is:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"sourceMap": true
},
"exclude": [
"node_modules"
]
}
In this example, compilerOptions contains the information that indicates to compiler what version of ECMAScript will be used and others that is exampled below:
target - ECMAScript version that need to be transcripted.
module - Specify module code generation like "None", "CommonJS", "System", etc
sourceMap - If need to generate the .map files.
The exclude property is an array that contains all folders you don't need to compile when this process start, in this case node_modules is not necessary, because is a folder that contains our references.
Creating the Typescript files
The next step is start to code using Typescript, now we need to create the App.ts inside the "src" folder, in this file we have the configuration of middlewares, routes and we expose the express to use in others points of the system.
import * as express from 'express';
import * as logger from 'morgan';
import * as bodyParser from 'body-parser';
import { Utility } from './classes/helpers/utility';
class App {
public express: express.Application;
constructor() {
this.express = express();
this.middleware();
this.routes();
}
private middleware(): void {
this.express.use(logger('dev'));
this.express.use(bodyParser.json());
this.express.use(bodyParser.urlencoded({ extended: false }));
}
private routes(): void {
this.setAllRoutes();
this.setDefaultRoute();
}
private setAllRoutes(): void {
const utility = new Utility();
let arrayFileRoutes = utility.readRecursiveDirectory('routes');
arrayFileRoutes.forEach(file => {
let routeInstance = require(`./${file.replace(/\.[^/.]+$/, '')}`);
let fn = `/api${file.replace('routes', '').split('\\').join('/').replace(/\.[^/.]+$/, '')}`;
this.express.use(fn, routeInstance.default.getRouter());
console.log(`Route ${fn} --> OK`);
});
}
private setDefaultRoute(): void {
this.express.get('/api', (req, res, next) => {
res.status(200).json({
title: 'API Test',
version: '1.0.0',
path: '/api/v1'
});
});
}
}
export default new App().express;
In this project, the routes will be created based where the routes files is located, like this example below:
In this image we have the routes folder and inside it we have the v1 and v2 folders, this is a common pattern in API development to create folders to specify the version of api resource, work in this way allow us to modify the api behavior without create errors in applications that used this API before.
With this automatic defined route, the log when start the application will be this way:
All routes, based where the file is located in routes folder was automatically created by App.ts, the folder structure was read and was created all necessary routes.
And finally, let's to create the Index.ts that is the file that have the responsibility to start definitively our application:
import * as http from 'http';
import * as debug from 'debug';
import App from './App';
debug('ts-express:server');
const port = normalizePort(process.env.PORT || 3000);
App.set('port', port);
const server = http.createServer(App);
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
console.log(`Online in port ${port}`);
function normalizePort(val: number | string): number | string | boolean {
let port: number = (typeof val === 'string') ? parseInt(val, 10) : val;
if (isNaN(port))
return val;
else if (port >= 0)
return port;
else
return false;
}
function onError(error: NodeJS.ErrnoException): void {
if (error.syscall !== 'listen')
throw error;
let bind = (typeof port === 'string') ? 'Pipe ' + port : 'Port ' + port;
switch (error.code) {
case 'EACCES':
console.error(`${bind} requires elevated privileges`);
process.exit(1);
break;
case 'EADDRINUSE':
console.error(`${bind} is already in use`);
process.exit(1);
break;
default:
throw error;
}
}
function onListening(): void {
let addr = server.address();
let bind = (typeof addr === 'string') ? `pipe ${addr}` : `port ${addr.port}`;
debug(`Listening on ${bind}`);
}
Routes, Controllers and Services!
Below we have the basic routes created to be the point for consults all client's information, new functions can be added to add more actions to your API's endpoints:
import * as express from 'express';
import { ClientController } from '../../controllers/v1/ClientController';
import { IRouterApi } from '../../classes/interfaces/IRouterApi';
class ClientRouter implements IRouterApi {
public getRouter(): express.Router {
const clientController = new ClientController();
let router = express.Router();
router.route('/').get(clientController.getClients);
return router;
}
}
export default new ClientRouter();
And our IRouterApi interface:
import { Router } from "express";
export interface IRouterApi {
getRouter(): Router;
}
Now we have define the routes and your interface, we need to create the controller and service for we can test our endpoint and see the how the client's routes will return, let's take a look of Client controller:
import { NextFunction, Request, Response } from "express";
import { ClientService } from '../../services/v1/ClientService';
export class ClientController {
public getClients(req: Request, res: Response, next: NextFunction): void {
const clientService = new ClientService();
res.status(200).json(clientService.searchClients());
}
}
In this file, basically, we create a basic function that search all clients, we instantiate a ClientService and we returns this values in the response of our application.
export class ClientService {
public searchClients(): Array<any> {
return [
{
message: 'Client name'
}
];
}
}
Finally we have the ClientService that contains a function that returns an array with a simple object!
The test
After this journey to create our first basic backend using Typescript, we need to test if the response is in the way that we need, in our example, I will use the Postman, but you can use any of application that your preference.
And the response...
I'll be back!
In the next posts, we going to use this Typescript basic backend to apply other technologies and concepts. I hope that all of you have to like this post and I see you soon!
Bye!
References
[1] https://www.typescriptlang.org/
[2] https://ionicframework.com/docs/v3/developer-resources/typescript/
[3] https://medium.com/swlh/the-major-benefits-of-using-typescript-aa8553f5e2ed
[4] https://www.typescriptlang.org/docs/handbook/compiler-options.html
Top comments (1)
i think nestjs will be fair