Express.js is a Node.js framework for creating maintainable and fast backend web applications in JavaScript. In the fast-paced world of web development, Express has become a leading technology for building modern web applications due to its flexible and minimalist architecture.
Join us in this adoption guide and get ready to learn everything you need to know about Express and when to use it in your projects!
Express is a versatile, unopinionated, and minimalistic web framework for building scalable and robust backend applications in Node.js. Over time, it has become so popular that most resources about Node.js actually refer to Express.
Let’s now take a step back and explore its history before seeing how it works and how to get started with it.
The history of Express began in 2010, during the early days of Node.js. While Node.js brought JavaScript to server-side programming with an asynchronous and event-driven approach, it lacked key features like routing, templating, middleware, and robust error handling.
Legendary open source developer TJ Holowaychuk decided to make the first move. Inspired by the Ruby philosophy embodied in Sinatra, he sought to create a web framework that was both simple and expressive. He borrowed some concepts from other web frameworks and created Express.
As the Node.js ecosystem flourished, so did Express. In 2014, the management rights of the technology were acquired by StrongLoop, which caused discontent and concerns about the project becoming more commercially oriented.
In response, some developers forked Express to create alternative frameworks like Koa. StrongLoop was later acquired by IBM in 2015, which decided to place Express in the Node.js Foundation incubator to ensure long-term sustainability and community involvement.
Express gained popularity and has become a cornerstone of the MEAN, MERN, and MEVN stacks. Since the release of its fourth major version, Express.js has undergone significant enhancements. Version 4 introduced a new router, improved error handling, and a more modular architecture.
The community is currently working on Express 5, which will introduce features like native support for promises and async/await programming.
Express remains a pillar of the Node.js ecosystem, shaping the evolution of web frameworks and influencing projects both within and outside its community.
Express.js operates as a middleware-based library designed for building robust backend applications in Node.js. At its core, the framework revolves around the concept of routes.
Routes define how the application responds to client requests for specific URLs and HTTP methods. They can perform several operations, from serving static files to executing intricate business logic. That means an Express application can be both an API backend and a web server for hosting static and dynamic web pages.
The framework has a modular architecture, allowing developers to seamlessly define and register middleware functions. These can be applied across all routes or selectively to specific endpoints to handle specific tasks, such as API key authentication and error handling.
This unique combination of routing and middleware makes Express.js an adaptable solution for building powerful Node.js applications.
Starting an Express application is a simple process that takes just a few steps.
First, initialize a Node.js project:
npm init -y
Then, add Express to the project’s dependencies:
npm install express
Next, create an index.js
JavaScript file and define a basic Express server with a single route:
const express = require('express') // initialize an Express application const app = express() // define a simple "/api/hello-world" route app.get('/api/hello-world', (req, res) => { res.json('Hello, World!') }) // start the server const PORT = process.env.PORT || 3000 app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`) })
Execute the command below to launch the app:
node index.js
The http://localhost:3000/api/hello-world
endpoint will now respond with the following message:
'Hello, World!'
Great, you just set up an Express project!
Here, you will examine how Express behaves in the main aspects to consider when evaluating a web framework.
Express.js is well-known as a fast and responsive technology, with endpoints responding in just a few milliseconds on average. The main reason is its lightweight architecture.
It also relies on the non-blocking I/O nature of Node.js to efficiently handle concurrent requests. This enables Express to deal with thousands of simultaneous requests without significant performance degradation.
The main performance limitation of Express is that Node.js uses only one CPU core by default. That can become a problem with CPU-intensive tasks, which can easily saturate the resources of the underlying machine.
After all, if your server machine has multiple CPUs, you would like to use all of them. The solution is called “clustering,” a technique that allows the creation of child processes that run simultaneously by sharing the same server port.
Express.js is also known for its simplicity, minimalism, and flexibility. In particular, it comes with a straightforward API that provides building blocks such as routing, middleware, and other components to get a web application up and running quickly.
Express offers an unopinionated approach to web development, which means you’re free to structure your application as you prefer. That’s a nice option to have, but may also scare off some developers.
Following best practices for organizing your Express.js project structure enables you to benefit from the flexibility of Express without becoming uncertain or overwhelmed.
As of this writing, the express
npm package has an unpacked size of about 200kB. This small size is due to the design philosophy of Express.js, which involves a lean and minimal core.
The minimal core contributes to a lighter overall bundle size compared to more feature-heavy frameworks. For example, a bundled Spring Boot application with basic web capabilities weighs about 18 MB.
On the other hand, having a lightweight core means you might have to install additional dependencies based on your project’s requirements. While that impacts the overall bundle size of your backend application, the flexibility to include only what is really required makes Express a great composable framework.
According to Statista, Express is one of the most used backend web frameworks in the world. Nearly 20 percent of developers surveyed reported using it. That makes it second only to Node.js — the technology Express is based on.
Since Node.js is estimated to have several million developers worldwide, it is safe to say that the Express community boasts millions of users as well. No wonder the official Express repository on GitHub has more than 60k stars!
On top of that, Express is backed by a rich ecosystem of libraries. You can use many of the libraries available via npm, which lists over 1.3 million packages. The “Express” tag alone on npmjs.com counts more than six thousand libraries.
The Express API and syntax are designed to be accessible to both beginners and experienced developers. The learning curve is gentle, and it usually takes only a few weeks to master its main aspects, such as routing and the middleware mechanism. After all, it would not be so popular if it was difficult to learn.
That being said, some extra issues may arise when integrating it with TypeScript. To avoid that, follow our tutorial on how to set up TypeScript with Node.js and Express.
For such a popular framework, the official Express documentation is not the best. The project is run only by volunteers, which is clear from the documentation site. It does a decent job providing guidance on features and usage, but it could be much better.
In contrast, the official API reference for Express has improved a lot over time and is now a complete and comprehensive resource.
What makes the difference is the vast community supporting the technology. There are thousands of questions and answers on Stack Overflow and many useful, in-depth blog posts. It’s quite difficult to come across problems that haven’t already been documented and addressed by other developers.
The unopinionated nature of Express.js implies that it doesn’t impose any specific technology. For instance, it integrates seamlessly with databases like MySQL and PostgreSQL, various template engines, and web coding tools like Prettier and ESlint.
These are just a few examples, but keep in mind that Express can integrate with many other technologies. The main advantage of this characteristic is that developers are free to adopt the tools, libraries, and modules best suited to their needs.
Let’s dig into the main features of Express.js by describing them, learning how they work, and demonstrating their use.
In a backend server, routing refers to how the application endpoints respond to client requests. To define routes, the Express app
object exposes the following methods corresponding to the HTTP methods:
app.get()
: To define GET requestsapp.post()
: To define POST requestsapp.put()
: To define PUT requestsapp.patch()
: To define PATCH requestsapp.delete()
: To define DELETE requestsThese routing methods accept an endpoint string and a callback function. The callback is executed when the Express application receives a request that matches the specified endpoint string and HTTP method.
For example, you can define a GET
/api/users/:id
endpoint as follows:
app.get('/api/users/:id', (req, res) => { // read the id parameter from the URL const userId = req.params.id // retrieve paginated users const users = getUsers(pageSize, pageOffest) // getUsers() is omitted for brevity... // serialize the paginated users to JSON format res.json({ user: user }) })
The params
property of the req
object exposes the values of the named parameters specified in the REST endpoint. Thus, when calling the API with /api/users/1
endpoint, the req.params.id
value is 1
.
To access the query parameters, use the query
property instead. req.json()
serializes the JavaScript object passed as a parameter into JSON and returns it to the client. To return data in other formats, you can use req.send()
as specified in the docs.
Similarly, here’s how you can define a POST /api/users
endpoint that accepts a body with JSON data to create a new user:
app.post('/api/users', (req, res) => { // extract the information from the body const { username, email } = req.body // create a new user createUser(username, email) // createUser() is omitted for brevity... // return a "201 Created" empty response res.status(201).json() })
By default, req.body
is always undefined
. It gets populated only when you use body-parsing middleware such as express.json()
or express.urlencoded()
. If you‘re unfamiliar with Express middleware, we’ll cover it later in this guide.
Keep in mind that you can customize the routes, middleware, and handlers in your project based on your specific API requirements. Also, you can organize your routes in controller files for better maintainability and readability.
Connecting to a database to retrieve and store data is a crucial aspect of backend development. Express.js does not enforce a specific database choice, allowing developers to connect to any database technology.
Manually managing database connections is tricky, and developers usually rely on object-relational mapping (ORM) or object-document mapping (ODM) libraries. The vast Express ecosystem offers two notable options for facilitating database interactions:
The steps required to get started with these technologies are:
For example, after defining the User
model in Sequelize, retrieving all users from the database is as simple as writing the following:
const users = await User.findAll()
An Express middleware is a function that has access to the request object (req
), the response object (res
), and the function to proceed in the request-response cycle (next
).
Middleware modules can perform various tasks while an HTTP request is processing, such as modifying the request or response objects, terminating the request-response cycle, or calling the next middleware function. Note that the same endpoint can be associated with more than one middleware.
Here is how you can define a middleware function in Express:
const loggingMiddleware = (req, res, next) => { // log the body of the request console.log(req.body) // call the next middleware next() }
The next()
function at the end of the middleware definition passes control to the next function in the endpoint definition stack. If a middleware function does not call next()
, the request-response cycle is terminated and no further functions in the stack are executed.
Another way to terminate the request-response cycle is to send a response to the client using methods like res.send()
or res.json()
.
A middleware can be registered globally to all routes with app.use()
:
app.use(loggingMiddleware)
You can also choose to only pass a middleware to some endpoints as below:
app.get('api/v1/users/:id', loggingMiddleware, /* other middleware..., */ (req, res) => { // ... })
Bear in mind that middleware functions are executed in the order they are registered using app.use()
or in the specific routing methods.
Express.js supports two types of middleware:
req
, res
, and next
parameters. They usually perform tasks such as logging, authentication, data parsing, and moreerr
, req
, res
, and next
parameters. They are called only when an error occurs and are specifically designed to handle errorsSome of the most used Express middleware libraries are:
cors
: To define cross-origin policies for requests between browsers and the servermulter
: To deal with multipart/form-data
requests, which are generally used for file uploadsconnect-timeout
: To define a timeout to requests, terminating them if they take too long to processhelmet
: To secure your application by setting standard-based security HTTP headersexpress-rate-limit
: To protect against DoS attacks by limiting the number of requests the same client can make in a limited time spanHTTP is a stateless protocol, meaning it doesn’t retain information about the state of the communication between client and server. Cookies were introduced to address that limitation, allowing both the client and the server to maintain state between requests.
In web development, cookie management is essential for various purposes, such as tracking user sessions.
The most common Express middleware library for handling cookies is cookie-parser
. This package provides a middleware function that parses the Cookie
header and populates req.cookies
with an object containing the cookie’s <name, value>
pairs.
After installing this package, register the cookie-parser
middleware as below:
const express = require('express') const cookieParser = require('cookie-parser') const app = express() app.use(cookieParser())
You can now access the cookie
property from req
as in the following snippet:
app.post('/set-cookie', (req, res) => { // instruct the client to set a cookie // for the specified domain res.cookie('user', 'John Doe', { domain: '.example.com', path: '/' }) res.send('Cookie set!') }) app.get('/hello', (req, res) => { const username = req.cookies.user // "John Doe" res.send(`Hello, ${username}!`) // "Hello, John Doe" })
res.cookie()
is the method provided by Express to define a cookie.
Another useful middleware package that follows a similar approach to cookie-parser
is express-session
. This library provides a middleware function that adds a session
property to req
to store session data.
The express-session
middleware function defines a cookie that only contains the session ID, while the session data is stored directly on the server. That is a great approach to secure session management in Express.
By default, Express.js comes with a simple error handling middleware. This intercepts and handles errors that occur during the request-response cycle, returning a HTML status code of 500 with the message Internal Server Error
.
To override that behavior, you can define a custom error middleware function as in the following example:
app.use((err, req, res, next) => { // log the error object console.error(err) // return a 500 response with a custom message res.status(500).json('Something went wrong!') })
Bear in mind that Express doesn’t automatically catch all errors occurring in route handlers and middleware functions — you need to ensure this manually.
Errors that occur in synchronous code are intercepted and passed by default to the error-handling middleware. For errors occurring in asynchronous code, you must instead pass them to the next()
function as shown below:
app.get('/api/users/stats', (req, res, next) => { fs.readFile('/stats.csv', (err, data) => { if (err) { // propagate the error to the error-handling controller next(err) } else { res.send(data) } }) })
This is the only way Express will catch and process them.
User authentication is a fundamental aspect of building secure web applications. Express offers flexibility in implementing authentication, supporting both of the following techniques:
You can explore the implementation details along with the pros and cons of these approaches in our guide on Node.js server-side authentication.
You can also use Express.js to serve static files such as images, CSS stylesheets, JavaScript files, HTML files, or any other assets. The framework provides a built-in middleware called express.static()
. This accepts a string argument with the path to the root directory from which to serve static assets.
Use express.static()
as shown below:
const express = require('express') const path = require('path') const app = express() // serve static files from the 'public' directory app.use(express.static(path.join(__dirname, 'public')))
Express will now publicly expose all files under the local public
folder. So, assume your public
directory contains the following files:
public ├── images │ └── bg.jpg ├── css │ └── style.css ├── js │ └── app.js ├── index.html └── robots.txt
Start the development server on port 3000 and you will be able to access them like so:
http://localhost:3000/images/bg.jpg
http://localhost:3000/css/style.css
http://localhost:3000/js/app.js
http://localhost:3000/index.html
http://localhost:3000/robots.txt
To serve static files from multiple folders, register express.static()
multiple times on different paths.
Templating refers to the process of dynamically generating HTML content by populating static structures with dynamic data. Express.js provides templating capabilities to create flexible and reusable HTML views to enhance server-side rendering.
Some of the most popular template engines supported by Express are:
Check out the documentation for a complete list of Express template engines.
By default, Express can interpret application/x-www-form-urlencoded
requests after registering the express.urlencoded
middleware:
app.use(express.urlencoded({ extended: true }))
You will now find the body data of an application/x-www-form-urlencoded
request in req.body
.
However, that approach does not work on multipart/form-data
requests, which are primarily used for uploading files. In this case, you need the Node.js middleware library multer
instead.
Scaffolding involves setting up the directory structure, files, and configurations of a new project. Even though setting up a new Express.js project is not complex, organizing it correctly and integrating it with popular libraries involves boilerplate operations.
For this reason, Express supports project scaffolding via the following libraries:
These tools improve productivity, help save time, and make it easier to organize code, manage dependencies, and adhere to best practices.
These are some of the most common use cases of Express.js:
The JavaScript web framework is so flexible that it adapts to many more real-world scenarios and applications. No wonder Express.js has been embraced by well-known enterprises such as Netflix, Uber, Fox Sports, Accenture, and IBM.
Express.js is not the only web framework available, and it has many competitors. It is time to see how it compares against other similar technologies!
The comparison aspects we’ll focus on in this section will include features, performance, community, and documentation.
Spring Boot is a powerful Java-based framework that simplifies the development of highly robust and scalable applications. In comparison to Express:
I’d recommend opting for Spring Boot for large-scale enterprise applications, but for simpler projects, I prefer Express.js for its composability, flexibility, and performance.
Laravel is an elegant full-stack PHP framework known for its expressive syntax and developer-friendly features. Let’s see how it stacks up to Express:
Choose Laravel for a more structured approach to web development or if you are looking for a full-stack PHP framework. Go for Express.js for faster responses, a larger community, and more freedom.
Flask is a lightweight Python web framework known for its simplicity and modularity. Here’s how Express and Flask compare:
Flask and Express.js are similar frameworks. Flask is great for very small projects because of its extreme modularity, while Express is overall faster and more suitable for general use cases.
Other Express alternatives based on Node.js are:
To see what these frameworks have to offer and how they compare with Express, read our in-depth comparison.
In this article, we covered what Express.js is and how it became one of the most popular web frameworks on the planet. We explored how it works, what its main features are, and how it compares to other popular web technologies. Express expertise unlocked!
The aim of this overview was to guide you in your consideration of Express.js for your next project. If you have any further questions about what Express is best for and how to use it efficiently, feel free to comment below.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.