- 1. TypeScript Express tutorial #1. Middleware, routing, and controllers
- 2. TypeScript Express tutorial #2. MongoDB, models and environment variables
- 3. TypeScript Express tutorial #3. Error handling and validating incoming data
- 4. TypeScript Express tutorial #4. Registering users and authenticating with JWT
- 5. TypeScript Express tutorial #5. MongoDB relationships between documents
- 6. TypeScript Express tutorial #6. Basic data processing with MongoDB aggregation
- 7. TypeScript Express tutorial #7. Relational databases with Postgres and TypeORM
- 8. TypeScript Express tutorial #8. Types of relationships with Postgres and TypeORM
- 9. TypeScript Express tutorial #9. The basics of migrations using TypeORM and Postgres
- 10. TypeScript Express tutorial #10. Testing Express applications
- 11. TypeScript Express tutorial #11. Node.js Two-Factor Authentication
- 12. TypeScript Express tutorial #12. Creating a CI/CD pipeline with Travis and Heroku
- 13. TypeScript Express tutorial #13. Using Mongoose virtuals to populate documents
- 14. TypeScript Express tutorial #14. Code optimization with Mongoose Lean Queries
- 15. TypeScript Express tutorial #15. Using PUT vs PATCH in MongoDB with Mongoose
Hello! In this new series, we build a RESTful application with TypeScript Express. The course is going to cover the fundamental concepts of the express framework and tools like MongoDB. Some basic knowledge of TypeScript would be useful, as we focus on Express here.
I will update the repository with the current state of the application as the course progresses. Feel free to give it a star! It is available at this address: mwanago/express-typescript. You can use it as a boilerplate if you would find it useful.
TypeScript Express tutorial #1
Express is a framework for Node.js used to build the backend for web applications. It is unopinionated, meaning that you can use it in a manner in which you see fit. In this tutorial, I present a way that works for me while working with the TypeScript Express.
Starting up the project
To get started we need to install necessary packages first using NPM.
1 2 |
npm init npm install typescript express ts-node |
If you would like to know more about NPM, check out Keeping your dependencies in order when using NPM
We start with an elementary tsconfig.json file:
tsconfig.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "compilerOptions": { "sourceMap": true, "target": "es2017", "outDir": "./dist", "baseUrl": "./src" }, "include": [ "src/**/*.ts" ], "exclude": [ "node_modules" ] } |
To run our project, we need to add a script in our package.json:
package.json
1 2 3 |
"scripts": { "dev": "ts-node ./src/server.ts" } |
As you can see, our app starts at the server.ts file in the src directory. Let’s start with the basics:
src/server.ts
1 2 3 4 5 6 7 8 9 |
import * as express from 'express'; const app = express(); app.get('/', (request, response) => { response.send('Hello world!'); }); app.listen(5000); |
- The express() function creates the Express application that we are going to interact with.
- The get function, putting it simply, attaches a callback function to the specified path for the HTTP GET requests.
- When someone makes a GET request for the specified path, the callback function runs.
- the request and response objects represent the HTTP request and response properties
- the response.send function causes the response to be sent to the client
- the listen function makes the app listen for a connection on the specified port.
- locally running app.listen(5000) causes our application to be accessible at http://localhost:5000
To test our application we use Postman. To install it, visit getpostman.com.
Let’s give it a try!
1 |
npm run dev |
Middleware
Middleware functions have access to the request and response objects. It can attach to any place in the request-response cycle. A third argument that middleware receives is the next function. When called, the next middleware in the chain is executed. An example of a middleware is the get callback that handles the HTTP GET request that we’ve written above. It is a very specific middleware that executes on a particular case. They can also perform more generic tasks. Let’s create a very simple logger middleware that will log to console what requests were made.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import * as express from 'express'; function loggerMiddleware(request: express.Request, response: express.Response, next) { console.log(`${request.method} ${request.path}`); next(); } const app = express(); app.use(loggerMiddleware); app.get('/hello', (request, response) => { response.send('Hello world!'); }); app.listen(5000); |
In this example, as soon as someone sends the GET request to the /hello path, “GET /hello” will be printed in the console in which the app runs. In fact, it runs even when someone requests a page that you don’t have a handler for.
Thanks to calling next(), the control of the request can be passed further. If you create a middleware that neither ends the request-response cycle (for example by sending a response) or calls the next function, the request will not finish with a valid response.
There are a lot of ready to use middlewares that you can attach to your application and you will have plenty of chances to see some of them in this course. A crucial one is the body-parser. It parses the body of the incoming request and makes it available under the request.body property. In this example, we use the bodyParser.json middleware that parses the json data.
1 |
npm install body-parser |
1 2 3 4 5 6 7 8 9 10 11 12 |
import * as express from 'express'; import * as bodyParser from 'body-parser'; const app = express(); app.use(bodyParser.json()); app.post('/', (request, response) => { response.send(request.body); }); app.listen(5000); |
The body was sent back to us thanks to running response.send(request.body). Without the body parser, the request.body property wouldn’t be accessible.
As we go further, we explain more advanced concepts connected to the middleware.
Routing
The app object has a set of functions that attach callbacks to HTTP requests performed to specified paths, just like the examples above with app.get and app.post. You can also attach callbacks to other HTTP methods like POST, PUT, PATCH and DELETE. You can look up a whole list of them in the documentation.
Another way to set up routing is to use the router object. Once you create a router object you can call the methods like get, put, patch and delete just like on the app object.
1 2 3 4 5 |
const router = express.Router(); router.get('/', (request, response) => { response.send('Hello world!'); }); |
The only thing left that is required is to use the router.
1 |
app.use('/', router); |
As you can see by the usage of app.use, the router instance is just a middleware that you can attach to your application.
The addresses of the routes are a combination of paths provided for the app.use and the router.METHOD.
1 2 3 4 5 |
router.get('/hello', (request, response) => { response.send('Hello world!'); }); app.use('/api', router); |
The code above results in creating a route /api/hello that responds with a text “Hello world!”.
Request
The request object contains information about the HTTP request, such as headers, the request query string, and parameters.
It inherits from http.IncomingMessage.prototype and therefore contains its fields and methods, aside from adding new ones.
1 |
http.IncomingMessage.prototype.isPrototypeOf(request); // true |
If you would like to know more about prototypes, check out Prototype. The big bro behind ES6 class
1 2 3 4 5 6 7 |
app.get('/', (request, response) => { response.send({ hostname: request.hostname, path: request.path, method: request.method, }); }); |
We continue to go deeper into the request object as the course progresses.
Response
The response object represents the HTTP response that the application sends when receiving an HTTP request.
It inherits from http.ServerResponse.prototype: http.ServerResponse.prototype.isPrototypeOf(response); // true
Its the most important method is called send. It sends the HTTP response so that the client can receive it. The function accepts different types of data: strings, objects (Array included), or Buffers. Send ends the response process with data, but you can also end it without any data using the end function.
The same as with the request, we dive more into the response object as we go.
Controllers
A common way of structuring an Express application is called Model-View-Controller. Some of the key components of MVC are controllers. They contain the logic of the application and deal with handling client requests. Since this course covers TypeScript Express, we use classes. For the sake of readable code, I also create a class for the app instance itself.
src/server.ts
1 2 3 4 5 6 7 8 9 10 11 |
import App from './app'; import PostsController from './posts/posts.controller'; const app = new App( [ new PostsController(), ], 5000, ); app.listen(); |
src/app.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import * as express from 'express'; import * as bodyParser from 'body-parser'; class App { public app: express.Application; public port: number; constructor(controllers, port) { this.app = express(); this.port = port; this.initializeMiddlewares(); this.initializeControllers(controllers); } private initializeMiddlewares() { this.app.use(bodyParser.json()); } private initializeControllers(controllers) { controllers.forEach((controller) => { this.app.use('/', controller.router); }); } public listen() { this.app.listen(this.port, () => { console.log(`App listening on the port ${this.port}`); }); } } export default App; |
src/posts/post.interface.ts
1 2 3 4 5 6 7 |
interface Post { author: string; content: string; title: string; } export default Post; |
src/posts/posts.controller.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import * as express from 'express'; import Post from './post.interface'; class PostsController { public path = '/posts'; public router = express.Router(); private posts: Post[] = [ { author: 'Marcin', content: 'Dolor sit amet', title: 'Lorem Ipsum', } ]; constructor() { this.intializeRoutes(); } public intializeRoutes() { this.router.get(this.path, this.getAllPosts); this.router.post(this.path, this.createAPost); } getAllPosts = (request: express.Request, response: express.Response) => { response.send(this.posts); } createAPost = (request: express.Request, response: express.Response) => { const post: Post = request.body; this.posts.push(post); response.send(post); } } export default PostsController; |
The code above results in creating the route named /posts that responds on the GET and POST request, storing and displaying the list of posts. In the upcoming parts of the Typescript Express course, we continue to use controllers to structure our code.
The getAllPosts and createAPost are arrow functions because they access properties of an instance of the class. Since they are passed to the router and not called directly, the context changes.
You can achieve the same result by calling
this.router.get(this.path, this.getAllPosts.bind(this))
If you would like to know more about the this keyword, check out What is “this”? Arrow functions
Summary
In this article, we’ve grasped the very basics of building RESTful applications with TypeScript Express. It covered starting up a project, the use of middleware, routing and what are the request and response objects. The code that we write is structured in controllers and available in the repository. I hope that you will find it helpful. The repository will grow a lot in the upcoming future because there are more parts of the tutorial coming.
I’ve found a few typos. For example at the beginning you wrote tsconfig.js instead of .json and later on you wrote import Post from ‘./post.interface’; instead of import Post from ‘./posts.interface’; Just minor stuff in an overall great tutorial.
Thanks, I fixed the
tsconfig.js
typo. The other file you’ve mentioned is calledposts.interface
because it exports the Post interface (the singular form), and not the Posts interface (the plural form). Thank you for contributing!Alberto pointed out a typo, he did not comment on the naming choice…
In src/posts/posts.controller.ts you have:
import Post from ‘./post.interface’;
And it should be:
import Post from ‘./posts.interface’;
That’s all…
Thanks. I got that one sorted out 🙂
Correct me if i’m wrong, but:
const post: Post = request.body;
isn’t that line useless? i can send whatever i want and the controller will still push the request body into the existing posts array. i’m new to TS, so maybe i’m missing something, but i would expect some kind of an error if the incoming request body does not match the Post interface type. what’s the move here if i want a strict checking of the request?
In this particular example, this line is mostly to increase readability and does not cause any validation of the data.
To perform a strict checking of the request take a look at the third part of the series: Error handling and validating incoming data.
Question: I’ve implemented this tutorial, but am having an issue…when I perform a GET, I am getting the posts that have been predefined. Good so far. Then I POST a new one and it regurgitates it back to me. Still Good. Then I perform a GET again and instead of only getting the predefined ones back, I’m now getting them, plus the new one. So my question is: controller classes aren’t re-instantiated per request? ( Sorry, very new to node and coming from an ASP.NET MVC background where “controllers” can handle per-request state. )
If we break down the example with the controller, we just attach a function to run when a specific URL is requested.
Thanks to our design, this function can be a method of a controller, but it does not have to be.
In the first example we run this piece of code:
As you can see, there is no controller involved. You could initialize a controller inside of this function and let it handle the request, but I don’t see benefit in doing that.
I hope that clears things up for you a bit.
how can we integrate Winston logger in this?
please make a tutorial about logger implementation.
Why my IntelliSense is telling me that “Cannot find module ‘express'” and the same for body-parser? If I place the files in the root folder it works fine, but in src it behaves weirdly. When I run this server it works.
Hey, you would need to add the following to the tsconfig.json
"moduleResolution": "node"
under the “compilerOptions”. I had the same problem, and when researching, it seems like typescript needs to know that we want to implement node modules – anyone correct me if that is wrong please.
At this point, thanks for the great article Marcin! Love it.
Cheers,
Dustin
I had the same problem but it worked after this minor fix in the import statement.
Use this
import express from ‘express’;
import bodyParser from ‘body-parser’;
Instead of this
import * as express from ‘express’;
import * as bodyParser from ‘body-parser’;
this tutorial is the nice and the best
Hey,
first of all great tutorial! Thanks for this!
I was wondering where we get the “express.Application” type from in the “app.ts” file at the time we declare “public app:”.
For anyone not so comfortable with TypeScript yet, this is coming from a “TypeScript Declaration File” which library authors can publish. Reference: http://www.typescriptlang.org/docs/handbook/declaration-files/consumption.html
However, in order to get the proper type from it, I believe we need to install the express declaration file to our dev dependencies with
npm install --save-dev @types/express
Could someone please confirm this?
At least before I hovered over “express.Application” I could see it’s type “any”, whereas after installing the declaration file, it now shows me “interface e.Application”
Cheers,
Dustin
Hi Marcin,
perfect tutorial. But now I´m interested in how to implement e.g. Swagger.io into your existing project to have an api-documentation in place.
Maybe it would be perfect if you write a new blog with implementing swagger.io into it.
Thx,
Alex
import express from 'express';
const app: express.Application = express();
app.use(...)
app.get(...)
app.listen(...)
I get “Property ‘use’ does not exist on type Application” error, and can not compile. If I change the app type to ‘any’, that would compile, but that defeats the purpose of the typing. Any ideas?
what is the type of a Controller should be? interface/class? so that all controllers follow the same structure, and also to use that type in
app.ts
instead of the ‘any’ type of ‘controllers’ argumentThis is amazing, took me so long to find a tutorial on how to use Class from typescript to incorporate with Express. I believe you also cover how to do middleware as well . This is great, thank you.
This tutorial suggest me a basic & best way about MVC structure, Using this concept, I have developed MVC folder structure in Express with CRUD Example. So, Thanks for sharing..
Hi, thanks for sharing. If is send a POST-request to my application, i got a blank object as response. Is there a update or something else as reason that body-parser wont work? (i know this tutorial is 2 years old, but it is one of the best that i found for this topic)
Hey @Yannik
If you are getting in trouble with body-parser you can use express.json instead i.e
this.app.use(express.json());
body-parser is deprecated.
I hope it helps
Happy coding :p
Hi Guy, do you identify type for controller ? due to above that’s any. thanks you so much with tutorial so nice
also add “moduleResolution”: “node”, in compilerOptions of tsconfig.json to resolve the “Cannot find module ‘express’. Did you mean to set the ‘moduleResolution’ option to ‘node’, or to add aliases to the ‘paths’ option?”
Wow!)
Nice, thank you for the tutorial, I’m lucky that found your cite)
What’s the type of
controllers
in the App class?Great content. But I still don’t understand why we need wrap App and Controllers in a class. This seens unnecessary because there are no multiple instances
Nice article!
However, the App code posted here has a problem: the error handler should be registered after the controller and not before. Otherwise, we run into trouble: error-route-get-requires-callback-functions-but-got-a-object-undefined…
The code in github is ok.
Thanks!
Great post, I would like to implement this on PHP. Thanks for sharing