loading...
Cover image for Developing an Express Application Using TypeScript

Express Typescript Developing an Express Application Using TypeScript

aligoren profile image Ali GOREN ・4 min read

This post was first published on my blog.

Recently, I was working on TypeScript. I asked a question about TypeScript. Dev users helped me.

In this post, I'll show you how to create an express application using TypeScript.

Before you start, I'm so sorry for my grammar mistakes.

Express Framework

As you know, Express is a NodeJS web framework that works on the server.

Installation of Dependencies

npm i express pug ts-node typescript @types/express @types/node

These are dependencies.

Edit package.json and tsconfig.json files

The scripts section in the package.json will be like that;

"scripts": {
    "dev": "ts-node src/server.ts",
    "start": "ts-node dist/server.js",
    "build": "tsc -p ."
}

I'll be able to run the dev version of this application using the npm run dev command.

My tsconfig.json file will be like that;

{
    "compilerOptions": {
        "sourceMap": true,
        "target": "es6",
        "module": "commonjs",
        "outDir": "./dist",
        "baseUrl": "./src"
    },
    "include": [
        "src/**/*.ts"
    ],
    "exclude": [
        "node_modules"
    ]
}

Project Structure

This is our project structure;

- dist
- node_modules
- public
- src
- views
package.json
tsconfig.json

By the way, public and views folders are not necessary if you don’t need UI in your project. (For example API backend). The project will develop under the src folder.

- controllers
- interfaces
- middleware
app.ts
server.ts

The controllers folder will have route controllers and their interface files. The interfaces folder will have interface files. The middleware folder will have our middlewares.

Let's look at the app.ts and server.ts files

Application File src/(app.ts)

My application file is like that;

import * as express from 'express'
import { Application } from 'express'

class App {
    public app: Application
    public port: number

    constructor(appInit: { port: number; middleWares: any; controllers: any; }) {
        this.app = express()
        this.port = appInit.port

        this.middlewares(appInit.middleWares)
        this.routes(appInit.controllers)
        this.assets()
        this.template()
    }

    private middlewares(middleWares: { forEach: (arg0: (middleWare: any) => void) => void; }) {
        middleWares.forEach(middleWare => {
            this.app.use(middleWare)
        })
    }

    private routes(controllers: { forEach: (arg0: (controller: any) => void) => void; }) {
        controllers.forEach(controller => {
            this.app.use('/', controller.router)
        })
    }

    private assets() {
        this.app.use(express.static('public'))
        this.app.use(express.static('views'))
    }

    private template() {
        this.app.set('view engine', 'pug')
    }

    public listen() {
        this.app.listen(this.port, () => {
            console.log(`App listening on the http://localhost:${this.port}`)
        })
    }
}

export default App

As you can see, the constructor expects three parameters. In this logic, port and controller parameters should be required but I wasn't sure about it. I've also init the assets and templates in case you use the UI in your project.

Server File src/(server.ts)

My server file is like that;

import App from './app'

import * as bodyParser from 'body-parser'
import loggerMiddleware from './middleware/logger'

import PostsController from './controllers/posts/posts.controller'
import HomeController from './controllers/home/home.controller'

const app = new App({
    port: 5000,
    controllers: [
        new HomeController(),
        new PostsController()
    ],
    middleWares: [
        bodyParser.json(),
        bodyParser.urlencoded({ extended: true }),
        loggerMiddleware
    ]
})

app.listen()

In this file, we've imported our App class. We passed three parameters. The first one port number. Our app will run on port 5000.

The second one is the controllers parameter. Our controller classes will be here with the new keyword.

And the last one middleWares. If you're using bodyParser or similar plugins you can use the middleWares.

Our Simple Middleware (middleware/logger.ts)

import { Request, Response } from 'express'

const loggerMiddleware = (req: Request, resp: Response, next) => {

    console.log('Request logged:', req.method, req.path)
    next()
}

export default loggerMiddleware

This is a simple HTTP logger. It shows the HTTP verb and its path.

IControlerBase (interfaces/IControllerBase.interface.ts)

I thought so that every controller has to implement this interface.

interface IControllerBase {
    initRoutes(): any
}

export default IControllerBase

Our First Controller (controllers/home.controller.ts)

HomeController will be like that;

import * as express from 'express'
import { Request, Response } from 'express'
import IControllerBase from 'interfaces/IControllerBase.interface'

class HomeController implements IControllerBase {
    public path = '/'
    public router = express.Router()

    constructor() {
        this.initRoutes()
    }

    public initRoutes() {
        this.router.get('/', this.index)
    }

    index = (req: Request, res: Response) => {

        const users = [
            {
                id: 1,
                name: 'Ali'
            },
            {
                id: 2,
                name: 'Can'
            },
            {
                id: 3,
                name: 'Ahmet'
            }
        ]

        res.render('home/index', { users })
    }
}

export default HomeController

We've implemented the IControllerBase. So, we must follow its rules. In this example controller file, we're assuming that we have data from the database server. I sent this data to the (home/index.pug) file. This file is located under the views folder.

<!DOCTYPE html>
html(lang="en")
    head
        meta(charset="UTF-8")
        meta(name="viewport", content="width=device-width, initial-scale=1.0")
        meta(http-equiv="X-UA-Compatible", content="ie=edge")
        title Document
    body
        each user, index in users
            h2(onclick=`alert(${index})`)= user.name

This is our pug file. We've also implemented the initRoutes method. Because the boss(IControllerBase) wants that.

Let's start the app

npm run dev

With this command, we will be able to run our application. Our application works on http://localhost:5000.

Developing an Express Application Using TypeScript

You can also check the posts folder. In this project, you can use TypeORM or Sequelize.

You can find this project on GitHub: https://github.com/aligoren/express-typescript-test

Conclusion

I really loved to use TypeScript. Before this work, I never had an idea about how TypeScript works.

Thanks for reading.

Discussion

pic
Editor guide
Collapse
nickytonline profile image
Nick Taylor (he/him)

One thing you could do is also add nodemon into the mix.

GitHub logo remy / nodemon

Monitor for any changes in your node.js application and automatically restart the server - perfect for development

Nodemon Logo

nodemon

nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected.

nodemon does not require any additional changes to your code or method of development. nodemon is a replacement wrapper for node. To use nodemon, replace the word node on the command line when executing your script.

NPM version Travis Status Backers on Open Collective Sponsors on Open Collective

Installation

Either through cloning with git or by using npm (the recommended way):

npm install -g nodemon
Enter fullscreen mode Exit fullscreen mode

And nodemon will be installed globally to your system path.

You can also install nodemon as a development dependency:

npm install --save-dev nodemon
Enter fullscreen mode Exit fullscreen mode

With a local installation, nodemon will not be available in your system path. Instead, the local installation of nodemon can be run by calling it from within an npm script (such as npm start) or using npx nodemon.

Usage

nodemon wraps your application, so you…

There are VS Code recipes to handle this.

Specifically this one, github.com/microsoft/vscode-recipe...

Even if you don't use VS Code, the tips provided are still helpful (see the npm scripts in the repo).

One thing I would recommend is to not use ts-node for production as you currently are for your npm start script. The reason being is it is a debugging/dev tool, much like babel-node. Consider transpiling and running the dist/server.js with good ol' node.

Looking forward to your next post!

Collapse
aligoren profile image
Ali GOREN Author

Thanks, Nick.

I'm usually using the VS Code. I never heard the vscode-recipes before.

I'm trying to best :)

I don't remember clearly but I may have used nodemon before. It worked so slow on my computer. I know, it depended on my computer. I use a really old laptop.

Again I will say, these recipes are really helpful to me.

Thanks :)

Collapse
heritiermwalila profile image
Heritier Mwalila

Hey Ali, thanks for the post I've learned something as I am new to typescript.

I just found that if you want to import express instance you must do it this way

import express from 'express' not import * as express from 'express'

Typescript detect that as an error.

Collapse
sufianbabri profile image
Sufian Babri

Nice article. I've got a question and a suggestion. :)

The question: your "start" script is using ts-node, shouldn't it use node because it'd be an overkill otherwise? Maybe some performance issue?

The suggestion: in the following line, instead of not declaring the type of the parameter "next" which defaults to any, you can declare it of type NextFunction:

const loggerMiddleware = (req: Request, resp: Response, next) => {

So, to import NextFunction, you can replace import { Request, Response } from 'express' with:

import { Request, Response, NextFunction } from 'express'
Collapse
edportscher profile image
Edwin

I have a question, I am just learning typescript, I do not understand this.

private middlewares(middleWares: { forEach: (arg0: (middleWare: any) => void) => void; })

what does { forEach: (arg0: (middleWare: any) => void) => void; } this do?

why could you not do this?

private middlewares(middleWares: any)

Collapse
heritiermwalila profile image
Heritier Mwalila

@edwin , this is very easy to understand if you already understand type of interface in typescript
You can write the same thing like this
interface arg{
(ar)=>void
}
interface middlewareInter{
forEach:(argument:arg)=>void // look at forEach function in js how it written
}
private middlewares(middlew:middlewareInter){
}

it return void because don't wanna handle the result there. void is a type in typescript which just tell that the function does not return anything

Collapse
jappyjan profile image
jappyjan

When using express with typescript, the best u can do is to take a look into TS.ed.
For me, this is the absolute best solution, with decorators and a fairly great architecture.

Collapse
aligoren profile image
Ali GOREN Author

I didn't know Ts.ED. It's amazing. ❤

Collapse
whodges profile image
will

Thank you for the tutorial. Saved me hours of rummaging around google searches!

Collapse
josiasaurel profile image
Josias Aurel

Thanks for this informative post :thumbUp:

Collapse
alexvdvalk profile image
Alex v

Thanks for this tutorial. I managed to implement this with handlebars instead.