Express is one of the best frameworks for Node.js. It has great support and a bunch of helpful features. There are a lot of great articles out there, which cover all of the basics. However, this time I want to dig in a little bit deeper and share my workflow for creating a complete website. In general, this article is not only for Express, but for using it in combination with some other great tools that are available for Node developers.
To follow along with this tutorial, I'm assuming you're somewhat familiar with Node and have it installed on your system already.
At the heart of Express is Connect. This is a middleware framework, which comes with a lot of useful stuff. If you're wondering what exactly middleware is, here is a quick example:
const connect = require('connect'),
http = require('http');
const app = connect()
.use(function(req, res, next) {
console.log("That's my first middleware");
next();
})
.use(function(req, res, next) {
console.log("That's my second middleware");
next();
})
.use(function(req, res, next) {
console.log("end");
res.end("hello world");
});
http.createServer(app).listen(3000);
Middleware is basically a function which accepts request and response objects and a next function. Each piece of middleware can decide to respond by using a response object or pass the flow to the next function by calling the next callback. In the example above, if you remove the next() method call in the second middleware, the hello world string will never be sent to the browser. In general, that's how Express works.
There's also some predefined middleware, which of course can save you a lot of time. For example, Body parser parses request bodies and supports application/json, application/x-www-form-urlencoded, and multipart/form-data. And Cookie parser parses cookie headers and populates req.cookies with an object keyed by the cookie's name.
Express actually wraps Connect and adds some new functionality around it, like routing logic, which makes the process much smoother. Here's an example of handling a GET request in Express:
app.get('/hello.txt', function(req, res){
var body = 'Hello World';
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Content-Length', body.length);
res.end(body);
});
The source code for this sample site that we built is available on GitHub. Feel free to fork it and play with it. Here are the steps for running the site.
app directorynpm install
node app.js
There are two ways to set up Express. The first one is by placing it in your package.json file and running npm install.
{
"name": "MyWebSite",
"description": "My website",
"version": "0.0.1",
"dependencies": {
"express": "5.x"
}
}
The framework's code will be placed in node_modules, and you will be able to create an instance of it. However, I prefer an alternative option, by using the command-line tool. By using npx express-generator, you now have a brand new CLI instrument. For example, if you run:
npx express-generator --sessions --css less --hbs app
Express will create an application skeleton with a few things already configured for you. Here are the usage options for the npx express-generator command:
Usage: express [options] [dir]
Options:
--version output the version number
-e, --ejs add ejs engine support
--pug add pug engine support
--hbs add handlebars engine support
-H, --hogan add hogan.js engine support
-v, --view <engine> add view <engine> support (dust|ejs|hbs|hjs|jade|pug|twig|vash) (defaults to jade)
--no-view use static html instead of view engine
-c, --css <engine> add stylesheet <engine> support (less|stylus|compass|sass) (defaults to plain css)
--git add .gitignore
-f, --force force on non-empty directory
-h, --help output usage information
As you can see, there are just a few options available, but for me they are enough. Normally, I use Less as the CSS preprocessor and handlebars as the templating engine. In this example, we will also need session support, so the --sessions argument solves that problem. When the above command finishes, our project looks like the following:
/public
/images
/javascripts
/stylesheets
/style.less
/routes
/index.js
/users.js
/views
/index.hbs
/error.hbs
/layout.hbs
/app.js
/package.json
If you check out the package.json file, you will see that all the dependencies which we need are added here, although they haven't been installed yet. To do so, just run npm install, and a node_modules folder will pop up.
I realize that the above approach is not always appropriate. You may want to place your route handlers in another directory or something similar. But, as you'll see in the next few sections, I'll make changes to the structure that's already generated, and it's pretty easy to do. So you should just think of the npx express-generator command as a boilerplate generator.
For this tutorial, I designed a simple website of a fake company named FastDelivery. Here's a screenshot of the complete design:



At the end of this tutorial, we will have a complete web application, with a working control panel. The idea is to manage every part of the site in separate restricted areas. The layout was created in Photoshop and sliced into CSS (less) and HTML (handlebars) files. After the slicing, we have the following files and app structure:
/public
/images (there are several images exported from Photoshop)
/javascripts
/stylesheets
/home.less
/inner.less
/style.css
/style.less (imports home.less and inner.less)
/routes
/index.js
/templates
/index.hbs (home page)
/layout.hbs (template for every other page of the site)
/app.js
/package.json
Here's a list of the site's elements that we are going to administrate:
There are a few things we need to do to get everything ready to start.
We need to do two things to get the files ready. First, we need to remove and add the proper files to match the file structure above. Second, we need to edit app.js to work with our new file structure. We need to remove these two lines:
const usersRouter = require("./routes/users");
...
app.use("/users", usersRouter);
Now, we need to set up the configuration. Let's imagine that our little site should be deployed to three different places: a local server, a staging server, and a production server. Of course, the settings for every environment are different, and we should implement a mechanism which is flexible enough. As you know, every node script is run as a console program. So we can easily send command-line arguments which will define the current environment. I wrapped that part in a separate module in order to write a test for it later. Here is the /config/index.js file:
const config = {
local: {
mode: 'local',
port: 3000
},
staging: {
mode: 'staging',
port: 4000
},
production: {
mode: 'production',
port: 5000
}
}
module.exports = function(mode) {
return config[mode || process.argv[2] || 'local'] || config.local;
}
There are only two settings (for now): mode and port. As you may have guessed, the application uses different ports for the different servers. That's why we have to update the entry point of the site in app.js.
const config = require('./config')();
process.env.PORT = config.port;
To switch between the configurations, just add the environment at the end. For example:
npm start staging
Will run the server at port 4000.
Now we have all our settings in one place, and they are easily manageable.
I'm a big fan of Test-Driven Development (TDD). I'll try to cover all the base classes used in this article. Of course, having tests for absolutely everything would make this writing too long, but in general, that's how you should proceed when creating your own apps. One of my favorite frameworks for testing is uvu, because it is very easy to use and fast. Of course, it is available in the NPM registry:
npm install --save-dev uvu
Then, create a new script inside the "scripts" property in your package.json for testing.
"scripts": {
"start": "node ./bin/www",
"test": "uvu tests"
},
Let's create a tests directory which will hold our tests. The first thing that we are going to check is our configuration setup. We will call the file for testing configuration config.js, and put it in tests.
const { test } = require("uvu");
const assert = require("uvu/assert");
test("Local configuration loading", function () {
const config = require("../config")();
assert.is(config.mode, "local");
});
test("Staging configuration loading", function () {
const config = require("../config")("staging");
assert.is(config.mode, "staging");
});
test("Production configuration loading", function () {
const config = require("../config")("production");
assert.is(config.mode, "production");
});
test.run();
Run npm test and you should see the following:
config.js • • • (3 / 3) Total: 3 Passed: 3 Skipped: 0 Duration: 0.81ms
This time, I wrote the implementation first and the test second. That's not exactly the TDD way of doing things, but over the next few sections I'll do the opposite.
I strongly recommend spending a good amount of time writing tests. There is nothing better than a fully tested application.
A couple of years ago, I realized something very important, which may help you to produce better programs. Each time you start writing a new class, a new module, or just a new piece of logic, ask yourself:
How can I test this?
The answer to this question will help you to code much more efficiently, create better APIs, and put everything into nicely separated blocks. You can't write tests for spaghetti code. For example, in the configuration file above (/config/index.js) I added the possibility to send the mode in the module's constructor. You may wonder, why do I do that when the main idea is to get the mode from the command-line arguments? It's simple... because I needed to test it. Let's imagine that one month later I need to check something in a production configuration, but the node script is run with a staging parameter. I won't be able to make this change without that little improvement. That one previous little step now actually prevents problems in the future.
Since we are building a dynamic website, we need a database to store our data in. I chose to use MongoDB for this tutorial. Mongo is a NoSQL document database. The installation instructions can be found in the documentation, and because I'm a Windows user, I followed the Windows installation. Once you finish with the installation, run the MongoDB daemon, which by default listens on port 27017. So, in theory, we should be able to connect to this port and communicate with the MongoDB server. To do this from a node script, we need a MongoDB module/driver. To add it, just run npm install mongodb.
Next, we are going to write a test, which checks if there is a mongodb server running. Here is the /tests/mongodb.js file:
const { test } = require("uvu");
const { MongoClient } = require("mongodb");
test("MongoDB server active", async function () {
const client = new MongoClient("mongodb://127.0.0.1:27017/fastdelivery");
await client.connect();
});
test.run();
We don't need to add any assert statement since the code will already throw an error if there are any problems. The callback in the .connect method of the MongoDB client receives a db object. We will use it later to manage our data, which means that we need access to it inside our models. It's not a good idea to create a new MongoClient object every time we have to make a request to the database. Because of that, we should connect to the database in the initial server creation. To do this, in /bin/www, we need to change a few lines:
// Replace this
server.listen(port);
server.on("error", onError);
server.on("listening", onListening);
// With this
(async function () {
const { MongoClient } = require("mongodb");
const client = new MongoClient("mongodb://127.0.0.1:27017/fastdelivery");
await client.connect();
app.use(function (req, res, next) {
req.db = client.db;
next();
});
const server = http.createServer(app);
server.listen(port);
server.on("error", onError);
server.on("listening", onListening);
})();
Even better, since we have a configuration setup, it would be a good idea to place the MongoDB host and port in there and then change the connect URL to:
`mongodb://${config.mongo.host}:${config.mongo.client}/fastdelivery`
Now, all Express handlers will have the req.db property available, due to the middleware automatically running before each request.
We all know the MVC pattern. The question is how this applies to Express. More or less, it's a matter of interpretation. In the next few steps I'll create modules, which act as a model, view, and controller.
The model is what will be handling the data that's in our application. It should have access to a db object, returned by MongoClient. Our model should also have a method for extending it, because we may want to create different types of models. For example, we might want a BlogModel or a ContactsModel. So we need to write a new spec, /tests/base.model.js, in order to test these two model features. And remember, by defining these functionalities before we start coding the implementation, we can guarantee that our module will do only what we want it to do.
const { test } = require("uvu");
const assert = require("uvu/assert");
const ModelClass = require("../models/base");
const dbMockup = {};
test("Module creation", async function () {
const model = new ModelClass(dbMockup);
assert.ok(model.db);
assert.ok(model.setDB);
assert.ok(model.collection);
});
test.run();
Instead of a real db object, I decided to pass a mockup object. That's because later, I may want to test something specific, which depends on information coming from the database. It will be much easier to define this data manually.
module.exports = class BaseModel {
constructor(db) {
this.setDB(db);
}
setDB(db) {
this.db = db;
}
collection() {
if (this._collection) return this._collection;
return (this._collection = this.db.collection("fastdelivery-content"));
}
};
Here, there are two helper methods: a setter for the db object and a getter for our database collection.
The view will render information to the screen. Essentially, the view is a class which sends a response to the browser. Express provides a short way to do this:
res.render('index', { title: 'Express' });
The response object is a wrapper, which has a nice API, making our life easier. However, I'd prefer to create a module which will encapsulate this functionality. The default views directory will be changed to templates, and a new one will be created, which will host the Base view class. This little change now requires another change. We should notify Express that our template files are now placed in another directory:
app.set("views", path.join(__dirname, "templates"));
First, I'll define what I need, write the test, and then write the implementation. We need a module matching the following rules:
render method which accepts a data object.You may wonder why I'm extending the View class. Isn't it just calling the response.render method? Well, in practice, there are cases in which you will want to send a different header or maybe manipulate the response object somehow—for example, serving JSON data:
const data = { developer: "Krasimir Tsonev" };
response.contentType("application/json");
response.send(JSON.stringify(data));
Instead of doing this every time, it would be nice to have an HTMLView class and a JSONView class, or even an XMLView class for sending XML data to the browser. It's just better, if you build a large website, to wrap such functionalities instead of copy-pasting the same code over and over again.
Here is the spec for the /views/Base.js:
const { test } = require("uvu");
const assert = require("uvu/assert");
const ViewClass = require("../views/base");
test("View creation and rendering", function () {
let responseMockup = {
render: function (template, data) {
assert.is(data.myProperty, "value");
assert.is(template, "template-file");
},
};
let view = new ViewClass(responseMockup, "template-file");
view.render({ myProperty: "value" });
});
test.run();
In order to test the rendering, I had to create a mockup. In this case, I created an object which imitates Express's response object. Here is the /views/base.js class.
module.exports = class BaseView {
constructor(response, template) {
this.response = response;
this.template = template;
}
render(data) {
if (this.response && this.template) {
this.response.render(this.template, data);
}
}
};
Now we have three specs in our tests directory, and if you run npm test, everything should pass.
Remember the routes and how they were defined?
app.get('/', routes.index);
The '/' after the route—which, in the example above, is actually the controller—is just a middleware function which accepts request, response, and next.
exports.index = function(req, res, next) {
res.render('index', { title: 'Express' });
};
Above is how your controller should look in the context of Express. The express(1) command-line tool creates a directory named routes, which is for this.
Since we're not just building a tiny application, it would be wise if we created a base class, which we can extend. If we ever need to pass some kind of functionality to all of our controllers, this base class would be the perfect place. Again, I'll write the test first, so let's define what we need:
run method, which is the old middleware functionname property, which identifies the controllerSo just a few things for now, but we may add more functionality later. The test would look something like this:
const { test } = require("uvu");
const assert = require("uvu/assert");
const ControllerClass = require("../routes/base");
test("Children creation", function () {
class ChildClass extends ControllerClass {
constructor() {
super("my child controller");
}
}
const child = new ChildClass();
assert.ok(child.run);
assert.equal(child.name, "my child controller");
});
test("Children differentiation", function () {
class ChildAClass extends ControllerClass {
constructor() {
super("child A");
}
customProperty = "value";
}
class ChildBClass extends ControllerClass {
constructor() {
super("child B");
}
}
const childA = new ChildAClass();
const childB = new ChildBClass();
assert.is.not(childA.name, childB.name);
assert.not(childB.customProperty);
});
test.run();
And here is the implementation of /routes/base.js:
module.exports = class BaseController {
constructor(name) {
this.name = name;
}
run(req, res, next) {}
};
Of course, every child class should define its own run method, along with its own logic.
OK, we have a good set of classes for our MVC architecture, and we've covered our newly created modules with tests. Now we are ready to continue with the site of our fake company, FastDelivery.
Let's imagine that the site has two parts: a front-end and an administration panel. The front-end will be used to display the information written in the database to our end users. The admin panel will be used to manage that data. Let's start with our admin (control) panel.
Let's first create a simple controller which will serve as the administration page. Here's the /routes/admin.js file:
const BaseController = require("./base"),
View = require("../views/base");
module.exports = new (class AdminController extends BaseController {
constructor() {
super("admin");
}
run(req, res, next) {
if (this.authorize(req)) {
req.session.fastdelivery = true;
req.session.save(function (err) {
var v = new View(res, "admin");
v.render({
title: "Administration",
content: "Welcome to the control panel",
});
});
} else {
const v = new View(res, "admin-login");
v.render({
title: "Please login",
});
}
}
authorize(req) {
return (
(req.session &&
req.session.fastdelivery &&
req.session.fastdelivery === true) ||
(req.body &&
req.body.username === this.username &&
req.body.password === this.password)
);
}
})();
By using the pre-written base classes for our controllers and views, we can easily create the entry point for the control panel. The View class accepts the name of a template file. According to the code above, the file should be called admin.js and should be placed in /templates. The content would look something like this:
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<div class="container">
<h1>{{ content }}</h1>
</div>
</body>
</html>
In order to keep this tutorial fairly short and in an easy-to-read format, I'm not going to show every single view template. I strongly recommend that you download the source code from GitHub.
Now, to make the controller visible, we have to add a route to it in app.js:
const admin = require('./routes/admin');
...
app.all("/admin*", function (req,res,next) {
admin.run(req,res,next)
})
Note that we are not sending the Admin.run method directly as middleware. That's because we want to keep the context. If we do this:
app.all('/admin*', admin.run);
the word this in Admin will point to something else.
Every page which starts with /admin should be protected. To achieve this, we are going to use Express's middleware: Session. It simply attaches an object to the request called session. We should now change our Admin controller to do two additional things:
Here is a little helper function we can use to accomplish this:
authorize(req) {
return (
(req.session &&
req.session.fastdelivery &&
req.session.fastdelivery === true) ||
(req.body &&
req.body.username === this.username &&
req.body.password === this.password)
);
}
First, we have a statement which tries to recognize the user via the session object. Secondly, we check if a form has been submitted. If so, the data from the form is available in the request.body object, which is filled by the bodyParser middleware. Then we just check if the username and password match.
And now here is the run method of the controller, which uses our new helper. We check if the user is authorized, displaying the control panel if so, and otherwise we display the login page:
run(req, res, next) {
if (this.authorize(req)) {
req.session.fastdelivery = true;
req.session.save(function (err) {
var v = new View(res, "admin");
v.render({
title: "Administration",
content: "Welcome to the control panel",
});
});
} else {
const v = new View(res, "admin-login");
v.render({
title: "Please login",
});
}
As I pointed out at the beginning of this article, we have plenty of things to administrate. To simplify the process, let's keep all the data in one collection. Every record will have a title, text, picture, and type property. The type property will determine the owner of the record. For example, the Contacts page will need only one record with type: 'contacts', while the Blog page will require more records. So we need three new pages for adding, editing, and showing records. Before we jump into creating new templates, styling, and putting new stuff into the controller, we should write our model class, which stands between the MongoDB server and our application and of course provides a meaningful API.
// /models/content.js
const Base = require("./base"),
crypto = require("node:crypto");
class ContentModel extends Base {
constructor(db) {
super(db);
}
insert(data, callback) {
data.ID = crypto.randomBytes(20).toString("hex");
this.collection().insert(data, {}, callback || function () {});
}
update(data, callback) {
this.collection().update(
{ ID: data.ID },
data,
{},
callback || function () {}
);
}
getlist(callback, query) {
this.collection()
.find(query || {})
.toArray(callback);
}
remove(ID, callback) {
this.collection().findAndModify(
{ ID: ID },
[],
{},
{ remove: true },
callback
);
}
}
module.exports = ContentModel;
The model takes care of generating a unique ID for every record. We will need it in order to update the information later on.
If we want to add a new record for our Contacts page, we can simply use:
const model = new (require("../models/ContentModel"));
model.insert({
title: "Contacts",
text: "...",
type: "contacts"
});
So we have a nice API to manage the data in our MongoDB collection. Now we are ready to write the UI for using this functionality. For this part, the admin controller will need to be changed quite a bit. To simplify the task, I decided to combine the list of the added records and the form for adding/editing them. As you can see in the screenshot below, the left part of the page is reserved for the list and the right part for the form.



Having everything on one page means that we have to focus on the part which renders the page or, to be more specific, on the data which we are sending to the template. That's why I created several helper functions which are combined, like so:
this.del(req, function () {
this.form(req, res, function (formMarkup) {
this.list(function (listMarkup) {
v.render({
title: "Administration",
content: "Welcome to the control panel",
list: listMarkup,
form: formMarkup,
});
});
});
});
const v = new View(res, "admin");
It looks a bit ugly, but it works as I wanted. The first helper is a del method which checks the current GET parameters, and if it finds action=delete&id=[id of the record], it removes data from the collection. The second function is called form, and it is responsible mainly for showing the form on the right side of the page. It checks if the form is submitted and properly updates or creates records in the database. At the end, the list method fetches the information and prepares an HTML table, which is later sent to the template. The implementation of these three helpers can be found in the source code for this tutorial.
Here, I've decided to show you the function which handles the file upload in admin.js:
handleFileUpload(req) {
if (!req.files || !req.files.picture || !req.files.picture.name) {
return req.body.currentPicture || "";
}
const data = fs.readFileSync(req.files.picture.path);
const fileName = req.files.picture.name;
const uid = crypto.randomBytes(10).toString("hex");
const dir = __dirname + "/../public/uploads/" + uid;
fs.mkdirSync(dir, "0777");
fs.writeFileSync(dir + "/" + fileName, data);
return "/uploads/" + uid + "/" + fileName;
}
If a file is submitted, the node script .files property of the request object is filled with data. In our case, we have the following HTML element:
<input type="file" name="picture" />
This means that we could access the submitted data via req.files.picture. In the code snippet above, req.files.picture.path is used to get the raw content of the file. Later, the same data is written in a newly created directory, and at the end, a proper URL is returned. All of these operations are synchronous, but it's a good practice to use the asynchronous version of readFileSync, mkdirSync, and writeFileSync.
The hard work is now complete. The administration panel is working, and we have a ContentModel class, which gives us access to the information stored in the database. What we have to do now is to write the front-end controllers and bind them to the saved content.
Here is the controller for the Home page: /controllers/index.js
const BaseController = require("./")
const model = new (require("../models/content"))
module.exports = class HomeController extends BaseController{
constructor() {
super("Home")
this.content = null;
}
run(req, res, next) {
model.setDB(req.db);
const self = this;
this.getContent(function () {
const v = new View(res, "home");
v.render(self.content);
});
}
getContent(callback) {
const self = this;
this.content = {};
model.getlist(
function (err, records) {
// ... storing data to content object
model.getlist(
function (err, records) {
// ... storing data to content object
callback();
},
{ type: "blog" }
);
},
{ type: "home" }
);
}
};
The home page needs one record with a type of home and four records with a type of blog. Once the controller is done, we just have to add a route to it in app.js:
app.all("/", function (req, res, next) {
home.run(req, res, next);
});
Again, we are attaching the db object to the request. It's pretty much the same workflow as the one used in the administration panel.
The other pages for our front-end (client side) are almost identical, in that they all have a controller, which fetches data by using the model class and of course a route defined. There are two interesting situations which I'd like to explain in more detail. The first one is related to the blog page. It should be able to show all the articles, but also to present only one. So we have to register two routes:
app.all("/blog/:id", function (req, res, next) {
Blog.runArticle(req, res, next);
});
app.all("/blog", function (req, res, next) {
Blog.run(req, res, next);
});
They both use the same controller, Blog, but call different run methods. Pay attention to the /blog/:id string. This route will match URLs like /blog/4e3455635b4a6f6dccfaa1e50ee71f1cde75222b, and the long hash will be available in req.params.id. In other words, we are able to define dynamic parameters. In our case, that's the ID of the record. Once we have this information, we can create a unique page for every article.
The second interesting part is how I built the Services, Careers, and Contacts pages. It is clear that they use only one record from the database. If we had to create a different controller for every page, then we'd have to copy/paste the same code and just change the type field. There is a better way to achieve this, though, by having only one controller, which accepts the type in its run method. So here are the routes:
app.all("/services", function (req, res, next) {
Page.run("services", req, res, next);
});
app.all("/careers", function (req, res, next) {
Page.run("careers", req, res, next);
});
app.all("/contacts", function (req, res, next) {
Page.run("contacts", req, res, next);
});
And the controller would look like this:
module.exports = BaseController.extend({
name: "Page",
content: null,
run: function(type, req, res, next) {
model.setDB(req.db);
var self = this;
this.getContent(type, function() {
var v = new View(res, 'inner');
v.render(self.content);
});
},
getContent: function(type, callback) {
var self = this;
this.content = {}
model.getlist(function(err, records) {
if(records.length > 0) {
self.content = records[0];
}
callback();
}, { type: type });
}
});
Deploying an Express-based website is actually the same as deploying any other Node.js application:
npm install command should be run in order to install the new dependencies (if any).Keep in mind that Node is still fairly young, so not everything may work as you expected, but there are improvements being made all the time. For example, forever guarantees that your Node.js program will run continuously. You can do this by issuing the following command:
forever start yourapp.js
This is what I'm using on my servers as well. It's a nice little tool, but it solves a big problem. If you run your app with just node yourapp.js, once your script exits unexpectedly, the server goes down. forever simply restarts the application.
Now I'm not a system administrator, but I wanted to share my experience integrating node apps with Apache or Nginx because I think that this is somehow part of the development workflow.
As you know, Apache normally runs on port 80, which means that if you open http://localhost or http://localhost:80, you will see a page served by your Apache server, and most likely your node script is listening on a different port. So you need to add a virtual host that accepts the requests and sends them to the right port. For example, let's say that I want to host the site that we've just built on my local Apache server under the expresscompletewebsite.dev address. The first thing that we have to do is to add our domain to the hosts file.
127.0.0.1 expresscompletewebsite.dev
After that, we have to edit the httpd-vhosts.conf file under the Apache configuration directory and add:
# expresscompletewebsite.dev
<VirtualHost *:80>
ServerName expresscompletewebsite.dev
ServerAlias www.expresscompletewebsite.dev
ProxyRequests off
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
<Location />
ProxyPass http://localhost:3000/
ProxyPassReverse http://localhost:3000/
</Location>
</VirtualHost>
The server still accepts requests on port 80 but forwards them to port 3000, where node is listening.
The Nginx setup is much easier and, to be honest, it's a better choice for hosting Node.js-based apps. You still have to add the domain name in your hosts file. After that, simply create a new file in the /sites-enabled directory under the Nginx installation. The content of the file would look something like this:
server {
listen 80;
server_name expresscompletewebsite.dev
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $http_host;
}
}
Keep in mind that you can't run both Apache and Nginx with the above hosts setup. That's because they both require port 80. Also, you may want to do a bit of additional research about better server configuration if you plan to use the above code snippets in a production environment. As I said, I'm not an expert in this area.
Express is a great framework, which gives you a good starting point to begin building your applications. As you can see, it's a matter of choice on how you will extend it and what you will use to build with it. It simplifies the boring tasks by using some great middleware and leaves the fun parts to the developer.
This post has been updated with contributions from Jacob Jackson. Jacob is a web developer, technical writer, freelancer, and open-source contributor.
The Best Small Business Web Designs by DesignRush
/
2Create Modern Vue Apps Using Create-Vue and Vite
/
9Pros and Cons of Using WordPress
/
-1How to Fix the “There Has Been a Critical Error in Your Website” Error in WordPress
/
2How To Fix The “There Has Been A Critical Error in Your Website” Error in WordPress
How to Create a Privacy Policy Page in WordPress
/
8WordPress Website Maintenance Guide For Beginners
/
8How Long Does it Take to Learn JavaScript
/
1The Best Way to Deep Copy an Object in JavaScript
/
-2
4
-5Adding and Removing Elements From Arrays in JavaScript
/
-2Create a JavaScript AJAX Post Request: With and Without jQuery
/
85 Real-Life Uses for the JavaScript reduce() Method
/
-8How to Enable or Disable a Button With JavaScript: jQuery vs. Vanilla
/
4How to Enable or Disable a Button With JavaScript: jQuery vs Vanilla
/
-3Confirm Yes or No With JavaScript
/
-4How to Change the URL in JavaScript: Redirecting
/
-127 Best Tab and Accordion Widget Plugins for WordPress (Free & Premium)
/
1321 Best Tab and Accordion Widget Plugins for WordPress (Free & Premium)
/
-1130 HTML Best Practices for Beginners
/
-731 Best WordPress Calendar Plugins and Widgets (With 5 Free Plugins)
/
225 Ridiculously Impressive HTML5 Canvas Experiments
/
-4
1How to Implement Email Verification for New Members
/
3How to Create a Simple Web-Based Chat Application
/
730 Popular WordPress User Interface Elements
/
-3Top 18 Best Practices for Writing Super Readable Code
/
1Best Affiliate WooCommerce Plugins Compared
/
-1018 Best WordPress Star Rating Plugins
/
1510+ Best WordPress Twitter Widgets
/
220+ Best WordPress Booking and Reservation Plugins
/
5Working With Tables in React: Part Two
/
-2Best CSS Animations and Effects on CodeCanyon
/
-1730 CSS Best Practices for Beginners
/
4How to Create a Custom WordPress Plugin From Scratch
/
310 Best Responsive HTML5 Sliders for Images and Text… and 3 Free Options
/
-616 Best Tab and Accordion Widget Plugins for WordPress
/
-718 Best WordPress Membership Plugins and 5 Free Plugins
/
325 Best WooCommerce Plugins for Products, Pricing, Payments and More
/
210 Best WordPress Twitter Widgets
1 /
1112 Best Contact Form PHP Scripts for 2020
/
-220 Popular WordPress User Interface Elements
/
-410 Best WordPress Star Rating Plugins
/
-412 Best CSS Animations on CodeCanyon
/
-212 Best WordPress Booking and Reservation Plugins
/
120 Best WordPress Calendar Plugins and Widgets
/
-312 Elegant CSS Pricing Tables for Your Latest Web Project
/
824 Best WordPress Form Plugins for 2020
/
-2Getting Started With Django: Newly Updated Course
/
-1Create a Blog for Each Category or Department in Your WooCommerce Store
/
38 Best WordPress Booking and Reservation Plugins
/
-15Best Exit Popups for WordPress Compared
/
19Best Exit Popups for WordPress Compared
/
-311 Best Tab & Accordion WordPress Widgets & Plugins
/
-1012 Best Tab & Accordion WordPress Widgets & Plugins
1New Course: Practical React Fundamentals
/
2Short Course: Better Angular App Architecture With Modules
/
3Preview Our New Course on Angular Material
/
4Build Your Own CAPTCHA and Contact Form in PHP
/
-3Object-Oriented PHP With Classes and Objects
/
1Best Practices for ARIA Implementation
/
-5Accessible Apps: Barriers to Access and Getting Started With Accessibility
/
-4Dramatically Speed Up Your React Front-End App Using Lazy Loading
/
1715 Best Modern JavaScript Admin Templates for React, Angular, and Vue.js
/
-1715 Best Modern JavaScript Admin Templates for React, Angular and Vue.js
/
319 Best JavaScript Admin Templates for React, Angular, and Vue.js
/
3New Course: Build an App With JavaScript and the MEAN Stack
/
10Hands-on With ARIA: Accessibility Recipes for Web Apps
/
610 Best WordPress Facebook Widgets
13Hands-on With ARIA: Accessibility for eCommerce
/
6New eBooks Available for Subscribers
/
-8Hands-on With ARIA: Homepage Elements and Standard Navigation
/
11
-5Site Accessibility: Getting Started With ARIA
/
5How Secure Are Your JavaScript Open-Source Dependencies?
/
-3New Course: Secure Your WordPress Site With SSL
/
-2Testing Components in React Using Jest and Enzyme
/
-8Testing Components in React Using Jest: The Basics
/
-515 Best PHP Event Calendar and Booking Scripts
/
-3Create Interactive Gradient Animations Using Granim.js
/
11How to Build Complex, Large-Scale Vue.js Apps With Vuex
1 /
-1Examples of Dependency Injection in PHP With Symfony Components
/
-5Set Up Routing in PHP Applications Using the Symfony Routing Component
1 /
8A Beginner’s Guide to Regular Expressions in JavaScript
/
9Introduction to Popmotion: Custom Animation Scrubber
/
-6Introduction to Popmotion: Pointers and Physics
/
-10New Course: Connect to a Database With Laravel’s Eloquent ORM
/
-6How to Create a Custom Settings Panel in WooCommerce
/
-15Building the DOM faster: speculative parsing, async, defer and preload
1 /
420 Useful PHP Scripts Available on CodeCanyon
3 /
51Introduction to the Stimulus Framework
/
-12Single-Page React Applications With the React-Router and React-Transition-Group Modules
712 Best Contact Form PHP Scripts
1 /
-3Getting Started With the Mojs Animation Library: The ShapeSwirl and Stagger Modules
Getting Started With the Mojs Animation Library: The Shape Module
/
-1Getting Started With the Mojs Animation Library: The HTML Module
/
4Project Management Considerations for Your WordPress Project
/
-88 Things That Make Jest the Best React Testing Framework
/
6Creating an Image Editor Using CamanJS: Layers, Blend Modes, and Events
/
1New Short Course: Code a Front-End App With GraphQL and React
/
1Creating an Image Editor Using CamanJS: Applying Basic Filters
/
-12Creating an Image Editor Using CamanJS: Creating Custom Filters and Blend Modes
/
-1Modern Web Scraping With BeautifulSoup and Selenium
/
1Challenge: Create a To-Do List in React
1 /
-2Deploy PHP Web Applications Using Laravel Forge
/
-3Getting Started With the Mojs Animation Library: The Burst Module
/
4
-910 Things Men Can Do to Support Women in Tech
/
-2A Gentle Introduction to Higher-Order Components in React: Best Practices
/
-5A Gentle Introduction to HOC in React: Learn by Example
/
3A Gentle Introduction to Higher-Order Components in React
/
-6Creating Pretty Popup Messages Using SweetAlert2
/
7Creating Stylish and Responsive Progress Bars Using ProgressBar.js
/
1618 Best Contact Form PHP Scripts for 2022
/
-2How to Make a Real-Time Sports Application Using Node.js
/
-2Creating a Blogging App Using Angular & MongoDB: Delete Post
/
-17Set Up an OAuth2 Server Using Passport in Laravel
/
1Creating a Blogging App Using Angular & MongoDB: Edit Post
/
1Introduction to Mocking in Python
/
1Creating a Blogging App Using Angular & MongoDB: Show Post
/
10Creating a Blogging App Using Angular & MongoDB: Home
/
6
3Creating a Blogging App Using Angular & MongoDB: Login
/
12Creating Your First Angular App: Implement Routing
/
12Persisted WordPress Admin Notices: Part 4
/
-2Creating Your First Angular App: Components, Part 2
/
3Persisted WordPress Admin Notices: Part 3
/
4How Laravel Broadcasting Works
/
-1
7Persisted WordPress Admin Notices: Part 2
/
-2Create Your First Angular App: Storing and Accessing Data
/
6Persisted WordPress Admin Notices: Part 1
/
-1Error and Performance Monitoring for Web & Mobile Apps Using Raygun
/
6Using Luxon for Date and Time in JavaScript
7 /
-3How to Create an Audio Oscillator With the Web Audio API
/
-7How to Cache Using Redis in Django Applications
/
220 Essential WordPress Utilities to Manage Your Site
/
-6Introduction to API Calls With React and Axios
/
13Beginner’s Guide to Angular 4: HTTP
/
13Rapid Web Deployment for Laravel With GitHub, Linode, and RunCloud.io
/
8Beginners Guide to Angular 4: Routing
/
-10Beginner’s Guide to Angular 4: Services
/
1Beginner’s Guide to Angular 4: Components
/
1Creating a Drop-Down Menu for Mobile Pages
/
9Introduction to Forms in Angular 4: Writing Custom Form Validators
/
6Getting Started With Redux: Connecting Redux With React
/
14Getting Started With Redux: Learn by Example
/
14Getting Started With Redux: Why Redux?
/
11How to Auto Update WordPress Salts
/
-4How to Download Files in Python
/
-2
-19Eloquent Mutators and Accessors in Laravel
1 /
15
-11
1310 Best HTML5 Sliders for Images and Text
/
8Site Authentication in Node.js: User Signup
/
-2Creating a Task Manager App Using Ionic: Part 2
/
2Creating a Task Manager App Using Ionic: Part 1
/
1Introduction to Forms in Angular 4: Reactive Forms
/
2
4Introduction to Forms in Angular 4: Template-Driven Forms
/
424 Essential WordPress Utilities to Manage Your Site
/
-125 Essential WordPress Utilities to Manage Your Site
/
-10Get Rid of Bugs Quickly Using BugReplay
1 /
-16Manipulating HTML5 Canvas Using Konva: Part 1, Getting Started
/
610 Must-See Easy Digital Downloads Extensions for Your WordPress Site
/
-222 Best WordPress Booking and Reservation Plugins
/
-5Understanding ExpressJS Routing
/
-615 Best WordPress Star Rating Plugins
/
12Creating Your First Angular App: Basics
/
-1Inheritance and Extending Objects With JavaScript
/
-10Introduction to the CSS Grid Layout With Examples
1 /
3
5Performant Animations Using KUTE.js: Part 5, Easing Functions and Attributes
/
-6Performant Animations Using KUTE.js: Part 4, Animating Text
/
-10Performant Animations Using KUTE.js: Part 3, Animating SVG
/
-8New Course: Code a Quiz App With Vue.js
/
-2Performant Animations Using KUTE.js: Part 2, Animating CSS Properties
/
1
8Performant Animations Using KUTE.js: Part 1, Getting Started
/
110 Best Responsive HTML5 Sliders for Images and Text (Plus 3 Free Options)
/
-3Single-Page Applications With ngRoute and ngAnimate in AngularJS
/
-14Deferring Tasks in Laravel Using Queues
/
-3
2Site Authentication in Node.js: User Signup and Login
/
10Working With Tables in React, Part Two
/
-17Working With Tables in React, Part One
/
1How to Set Up a Scalable, E-Commerce-Ready WordPress Site Using ClusterCS
/
-2New Course on WordPress Conditional Tags
/
2
-11Building With Vue.js 2 and Firebase
6 /
11
-8
1Best Unique Bootstrap JavaScript Plugins
/
-6
-1Essential JavaScript Libraries and Frameworks You Should Know About
/
6
3Vue.js Crash Course: Create a Simple Blog Using Vue.js
/
-14Build a React App With a Laravel RESTful Back End: Part 1, Laravel 5.5 API
/
2API Authentication With Node.js
/
-4Beginner’s Guide to Angular: HTTP
/
8Beginner’s Guide to Angular: Routing
/
-4Beginners Guide to Angular: Routing
/
-4Beginner’s Guide to Angular: Services
/
2Beginner’s Guide to Angular: Components
/
1How to Create a Custom Authentication Guard in Laravel
/
5Learn Computer Science With JavaScript: Part 3, Loops
/
-3Build Web Applications Using Node.js
/
1Learn Computer Science With JavaScript: Part 4, Functions
/
5Learn Computer Science With JavaScript: Part 2, Conditionals
/
13Create Interactive Charts Using Plotly.js, Part 5: Pie and Gauge Charts
/
-8Create Interactive Charts Using Plotly.js, Part 4: Bubble and Dot Charts
/
15Create Interactive Charts Using Plotly.js, Part 3: Bar Charts
/
-10Awesome JavaScript Libraries and Frameworks You Should Know About
/
-6Create Interactive Charts Using Plotly.js, Part 2: Line Charts
/
-8Bulk Import a CSV File Into MongoDB Using Mongoose With Node.js
/
-5Build a To-Do API With Node, Express, and MongoDB
/
-8Getting Started With End-to-End Testing in Angular Using Protractor
/
11TypeScript for Beginners, Part 4: Classes
/
-15Object-Oriented Programming With JavaScript
/
410 Best Affiliate WooCommerce Plugins Compared
/
6Stateful vs. Stateless Functional Components in React
/
8Make Your JavaScript Code Robust With Flow
/
-2Build a To-Do API With Node and Restify
/
7Testing Components in Angular Using Jasmine: Part 2, Services
/
3Testing Components in Angular Using Jasmine: Part 1
/
-1Creating a Blogging App Using React, Part 6: Tags
/
-10React Crash Course for Beginners, Part 3
/
4React Crash Course for Beginners, Part 2
/
5React Crash Course for Beginners, Part 1
/
3Set Up a React Environment, Part 4
1 /
9Set Up a React Environment, Part 3
/
-9New Course: Get Started With Phoenix
/
-7Set Up a React Environment, Part 2
/
2Command Line Basics and Useful Tricks With the Terminal
/
8How to Create a Real-Time Feed Using Phoenix and React
/
-2Build a React App With a Laravel Back End: Part 2, React
/
2Build a React App With a Laravel RESTful Back End: Part 1, Laravel 9 API
/
-4Creating a Blogging App Using React, Part 5: Profile Page
/
-2Pagination in CodeIgniter: The Complete Guide
/
24JavaScript-Based Animations Using Anime.js, Part 4: Callbacks, Easings, and SVG
JavaScript-Based Animations Using Anime.js, Part 3: Values, Timeline, and Playback
Learn to Code With JavaScript: Part 1, The Basics
/
410 Elegant CSS Pricing Tables for Your Latest Web Project
/
-9Getting Started With the Flux Architecture in React
/
-2Getting Started With Matter.js: The Composites and Composite Modules
/
2Getting Started With Matter.js: The Engine and World Modules
/
1410 More Popular HTML5 Projects for You to Use and Study
/
-21Understand the Basics of Laravel Middleware
/
-1Iterating Fast With Django & Heroku
/
9Creating a Blogging App Using React, Part 4: Update & Delete Posts
/
5Creating a jQuery Plugin for Long Shadow Design
/
-12How to Register & Use Laravel Service Providers
2 /
-13Unit Testing in React: Shallow vs. Static Testing
/
4Creating a Blogging App Using React, Part 3: Add & Display Post
/
14Creating a Blogging App Using React, Part 2: User Sign-Up
20 /
-4Creating a Blogging App Using React, Part 1: User Sign-In
/
69 More Popular HTML5 Projects for You to Use and Study
/
15Creating a Grocery List Manager Using Angular, Part 2: Managing Items
/
89 Elegant CSS Pricing Tables for Your Latest Web Project
/
-3Dynamic Page Templates in WordPress, Part 3
/
-4Angular vs. React: 7 Key Features Compared
/
6Creating a Grocery List Manager Using Angular, Part 1: Add & Display Items
/
1New eBooks Available for Subscribers in June 2017
/
7Create Interactive Charts Using Plotly.js, Part 1: Getting Started
/
10The 5 Best IDEs for WordPress Development (And Why)
/
-233 Popular WordPress User Interface Elements
/
2New Course: How to Hack Your Own App
/
-11How to Install Yii on Windows or a Mac
/
3What Is a JavaScript Operator?
/
-7How to Register and Use Laravel Service Providers
/
9
waly Good blog post. I absolutely love this…