Like any kind of apps, JavaScript apps also have to be written well.
Otherwise, we run into all kinds of issues later on.
In this article, we’ll look at some best practices we should follow when writing Node apps.
Structure Express Applications
We should have the following folder structure for our Express app:
src/
config/
controllers/
providers/
services/
models/
routes.js
db.js
app.js
test/
unit/
integration/
server.js
(cluster.js)
test.js
We have the src
folder with the code that runs in production.
config
has config.
controllers
have the controllers.
providers
have the logic for the controller routes.
services
have the business logic.
models
have the database models.
routes.js
loads all routes.
db.js
has the database routes.
app.js
loads the Express app.
test
has the tests.
unit
has the unit tests.
integration
has the integration tests.
server.js
in the entry point of the Express app.
cluster.js
is an optional file for creating clusters.
test.js
is the main test file to run all the tests in the test
directory.
Improve Express.js Performance and Reliability
There are a few ways to improve performance and reliability.
Set NODE_ENV to production
We should set the NODE_ENV
environment variable to production
so we get the benefits of the production config.
It’s 3 times than in dev.
This is because there’s compression and caching to make our app faster.
We can either run:
export NODE_ENV=production
to set the environment variable
or:
NODE_ENV=production node server.js
to set the environment variable and run the app at the same time.
Enable Gzip Compression
We can enable gzip compression for assets to do compression on our assets.
We can install the compression
middleware by running:
npm i compression
Then we can use it by writing:
const compression = require('compression')
const express = require('express')
const app = express()
app.use(compression())
This isn’t the best way to do gzip compression since it uses resources on the Express app.
Instead, we can enable gzip in the Nginx instead to offload the work to the reverse proxy.
Always Use Asynchronous Functions
If we have anything other than some simple operations, we should probably use async code.
We should promises most of the time or async/await for short.
For example, we can write:
(async () => {
const foo = () => {
//...
return val
}
const val = await asyncFunction;
})()
We have an asyncFunction
that returns a promise, so we use await
to get the result.
We can’t run synchronous functions on different threads since Node is single-threaded.
Therefore, we can only use async code to run long-running operations.
We can also spawn child processes or create multiple instances of our app to run different tasks on different processes.
Logging Correctly
We should collect our logs in a central location.
This way, we can find them when we need them.
Winston and Morgan and useful logging packages that can integrate with other services for centralized logging.
We can also use some service like Sematext to do logging.
For example, we can write:
const { stLogger, stHttpLoggerMiddleware } = require('sematext-agent-express')
const express = require('express')
const app = express()
app.use(stHttpLoggerMiddleware)
app.get('/api', (req, res, next) => {
stLogger.info('An info.')
stLogger.debug('A debug.')
stLogger.warn('A warning.')
stLogger.error('An error.')
res.send('Hello World.')
})
We have the the sematext-agent-express
package which has a logger than logs to the Sematext service.
Then we get the logs in a nice dashboard with the service.
Conclusion
We can better structure our Express app and run our production Express app in production mode.
Also, logging can be easy with a centralized logging service.