Overview
If you are here I am assuming you already know what expressJs, javascript, node, and typescript are. I am also assuming that you have node and npm installed. So I will not go in deep about that.
In this article, we will go through the setup to get you started with expressJs with typescript. We also go into the details on what are the controllers, middleware, services, routes, etc.
Let’s get started!
Start new NPM project and install the dependencies
- Create a new folder for your project, open your terminal, and type:
npm init
This command will ask you for some input. Respond as you like (it’s not relevant for this guide).
- Now install the dependencies:
npm i express express-winston winston debug cors — save
- Install the dev dependencies
npm i @types/cors @types/debug @types/express @types/node nodemon source-map-support tslint typescript --save-dev
- add some scripts to your package.json so it looks like this
{
"name": "medium",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "tsc && node ./dist/app.js",
"dev": "export DEBUG=* && nodemon --ext ts --exec 'npm run start'",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"debug": "^4.3.1",
"express": "^4.17.1",
"express-winston": "^4.1.0",
"winston": "^3.3.3"
},
"devDependencies": {
"@types/cors": "^2.8.10",
"@types/debug": "^4.1.5",
"@types/express": "^4.17.11",
"@types/node": "^14.14.35",
"nodemon": "^2.0.7",
"source-map-support": "^0.5.19",
"tslint": "^6.1.3",
"typescript": "^4.2.3"
}
}
- tsconfig: Create in the root of your project a file named
tsconfig.jsonthat should have this:
{"compilerOptions": {"target": "es2016","lib": ["es6", "dom", "es2017", "esnext.asynciterable"],"allowSyntheticDefaultImports": true,"experimentalDecorators": true,"emitDecoratorMetadata": true,"moduleResolution": "node","module": "commonjs","sourceMap": true,"outDir": "dist","rootDir": "./src","allowJs": true,"baseUrl": ".","esModuleInterop": true,"resolveJsonModule": true,},"include": ["./src/**/*", "./test/**/*"],"exclude": ["node_modules"]}
This file tells the typescript “compiler” how it should output our javascript
Create the scaffold for the project
- Create the main folder that will contain all of our code
mkdir src
cd src
- Create the folders that will contain our code organized by responsibility
mkdir controllers services routes middlewares
- Finally, that create our entry point file
touch app.ts
Now it’s time to start coding
- src/app.ts
import express from 'express';
import * as http from 'http';
import cors from 'cors';
import {BaseRoutes} from './routes/BaseRoutes';
import {TestRoutes} from './routes/TestRoutes';
import debug from 'debug';
import { errorLoggerMiddleware, loggerMiddleware } from './middlewares/logger';
import { json } from 'body-parser';
const app: express.Application = express();
const server: http.Server = http.createServer(app);
const port = 3000;
const routes: Array<BaseRoutes> = [];
const debugLog: debug.IDebugger = debug('app');
// here we are adding middlewares
app.use(json());
app.use(cors());
app.use(loggerMiddleware);
app.use(errorLoggerMiddleware);
// here we are adding the UserRoutes to our array,
// after sending the Express.js application object to have the routes added to our app!
routes.push(new TestRoutes(app));
// this is a simple route to make sure everything is working properly
server.listen(port, () => {
debugLog(`Server running at http://localhost:${port}`);
routes.forEach((route: BaseRoutes) => {
debugLog(`Routes configured for ${route.getName()}`);
});
});
In our app.ts we created our server application. Is the entry point that connects everything. We start by declaring some constants that define some behavior of our server. After that, we attach some middlewares that will look at every HTTP request we make to the server. Now push the routes our routes files to the routes array, and start the server.
This file requires us to create now the routes and the middlewares:
- src/routes/baseRoutes.ts and src/routes/testRoutes.ts
import express from 'express';
export abstract class BaseRoutes {
app: express.Application;
name: string;
constructor(app: express.Application, name: string) {
this.app = app;
this.name = name;
this.configureRoutes();
}
getName() {
return this.name;
}
abstract configureRoutes(): express.Application;
}
import {BaseRoutes} from './BaseRoutes';
import express from 'express';
import { TestController } from '../controllers/testController';
export class TestRoutes extends BaseRoutes {
constructor(app: express.Application) {
super(app, 'TestRoutes');
}
configureRoutes() {
const testController = new TestController();
this.app.route('/').get(testController.index);
return this.app;
}
}
The BaseRoute.ts is just a class that will serve the base from all routes files and declares the configureRoute method that all routes must implement. In the testRoute file, we attach a controller method to our route.
- src/middlewares/logger.ts
import * as winston from 'winston';
import * as expressWinston from 'express-winston';
export const loggerMiddleware = expressWinston.logger({
transports: [
new winston.transports.Console()
],
format: winston.format.combine(
winston.format.colorize(),
winston.format.json()
)
});
export const errorLoggerMiddleware = expressWinston.errorLogger({
transports: [
new winston.transports.Console()
],
format: winston.format.combine(
winston.format.colorize(),
winston.format.json()
)
});
- src/controllers/testController.ts
import express from 'express';
export class TestController {
async index(req: express.Request, res: express.Response) {
res.status(200).send('Hello world');
}
}
At last, we created our controller file. Here is where our logic starts. In the future, you should also create a service class that handles all the business logic and where you should call the repositories to interact with the database.
Start the server
Now we are in condition to start our server. As we seen above, were two scripts created in ou package.json . One for start our server in dev mode and the other to use in production mode.
While developing you could use:
npm run dev
In production:
npm run start
Both scripts are responsible to compile the typescript code and distribute it from the newly created dist folder. The dev mode has a detailed log of what is happening to make debugging easier.
What’s next
The next articles should e about build a complete and functional rest API using this base we built here. Maybe using a database with and using an ORM to abstract the database.