npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

xpressjs

v1.0.36

Published

XpressJS is a set of tools helpful with creating NodeJS applications based on ExpressJS library.

Downloads

63

Readme

XpressJS

XpressJS is a set of tools helpful with creating NodeJS applications based on ExpressJS library. It assumes to use following libraries:

Installation

To add XpressJS library to your project, just use a command specified for your package manager:

  • npm:
npm i tomedio/xpressjs
  • yarn:
yarn add tomedio/xpressjs

Features

Parsing JSON body

XpressJS comes with a very simple solution to parse JSON body and to still have access to raw version of it. You just need to use its middleware in the main file like this:

const { jsonBody } = require('xpressjs')

// after creating application instance: const app = express()
app.use(jsonBody)

Then in your controllers you can access body as:

const { username } = req.body

In the example above you can fetch username field from an object being sent in a request's body.

Access to raw body

You can get access to raw version of the body if you need it eg. to validate integrity of request. You can find raw body in req.rawBody field.

In the example above you can see generating a hash based on request. It can be used to validate integrity if you receive a token generated by a sender:

const crypto = require('node:crypto')

const generatedToken = crypto.createHmac('sha256', secret).update(req.rawBody).digest('base64')

Prisma Client provider

It's required to keep only one instance of Prisma Client because it's treated as a separate connection to database. When an application is running for long time, it may happen that more instances can be created accidentally. If this number increases too much, limit of connections can be exceeded.

XpressJS comes with a singleton provider of Prisma Client. In any file you can use it as following:

const { db } = require('xpressjs')

const prisma = db.getPrismaClient()

Provider makes sure that in the application will be only one instance of Prisma Client.

Getting items list from database

It's a common task to fetch list of items from database. For pagination purposes we need two elements:

  • items found for the given page,
  • total items with applied filters - to calculate number of pages.

This problem can be solved in a very easy way using XpressJS. You can use for it getList method:

const { list } = require('xpressjs')

await list.getList(query, entityObject, pageNumber, pageSize)

The function takes parameters:

  • query - it's a query needed for PrismaORM and build like for being used by eg. findMany() method; it needs to contain where field, but also include and other fields;
  • entityObject - PrismaOrm entity object available in prisma object and generated by the ORM; look at the example below to learn more about it;
  • pageNumber - number of current page;
  • pageSize - number of items available on a single page.

Function getList() return object with following values:

  • items - fetched items for the current page;
  • totalItems - number of all items which can be found with the given criteria;
  • totalPages - number of all pages which are needed to paginate through all items with the given page size.

Fetching items with number of total items can be done this way:

const { list, db } = require('xpressjs')

const prisma = db.getPrismaClient()

// set query values
const categoryId = 5
const pageNo = 2
const pageSize = 20

// in async function
const posts = await list.getList(
  {
    where: {
      categoryId: req.query.categoryId
    },
    include: {
      author: true
    }
  },
  prisma.post,
  pageNo,
  pageSize
)

In the example above we assume we have Post and User models. Post has categoryId property. Post is related to users by a relation on author field. post constant contains object with fetched results.

Context

Usually applications are context-less - all values must be passed explicitly in the code. It's a good practice to improve code readability.

However, sometimes having context can enable new functionalities like request identifier in logs (described below). XpressJS uses express-http-context library internally to provide context to your applications.

It is very easy to use it. You just need to initialize context at start of initializing your app instance:

const { context } = require('xpressjs')

// create `app` instance
context.useContext(app)

// next initialization

Then you can use context in every piece of code. You can store data in context under a given key:

const { context } = require('xpressjs')

const user = {
  email: '[email protected]'
}
context.setContext('userData', user)

and later get the data in other place:

const { context } = require('xpressjs')

const user = context.getContext('userData')

You can also update just a part of context data under the key, if data is an object:

const { context } = require('xpressjs')

const newData = { name: 'John', surname: 'Doe' }
context.updateContext('userData', newData)

context.getContext('userData') // { email: "[email protected]", name: "John", surname: "Doe" }

Note: Context is NOT shared between any two separated API calls. It doesn't matter if calls are done in parallel or one by one.

Logging

XpressJS offers you a global logger which can be configured one time and then can be used in all places where it's needed. Basic logger from the library can be used without any additional configuration. It will just use console transport (with handling exceptions) as output and preconfigured formatters.

Default preconfigured logger can be used like this:

const { logger } = require('xpressjs')

logger.default.info('Successfull usage of default logger')

or to have Logger instance directly under logger variable:

const {
  logger: { default: logger }
} = require('xpressjs')

logger.info('Successfull usage of default logger')

Logs format

You can configure logger on your own and determine custom logs format. However, for default logger the format is already defined as: [timestamp][app-name][level][request-id]: MESSAGE METADATA

  • timestamp - date and time when log has been saved in ISO format
  • app-name - name of application taken from package.json file (name property)
  • level - log's level, eg. info, debug, error, etc.
  • request-id - optional request identifier; if not enabled, then it's not available in logs
  • MESSAGE - content of log's message
  • METADATA - optional JSON serialized with other data passed to the logger while logging

Configure a logger

Logger functionality is available under logger property imported from XpressJS. There is a method - logger getter to get instance of logger. Getter function takes one optional parameter. It is options object, which can take the same values as createLogger from winston library. Passed options object is merged with default configuration provided by XpressJS.

Note: You can configure logger in a separate file, eg. /src/config/logger.js and import it in all places in your application.

Example configuration for Winston logger:

// /src/config/logger.js
const { transports } = require('winston')
const { logger } = require('xpressjs')

module.exports = logger.getLogger({
  transports: [
    new transports.Console({
      handleExceptions: false
    })
  ]
})

Then you can import this logger eg. like this:

const logger = require('../config/logger')

You may also use preconfigured default logger:

const { logger } = require('xpressjs')

module.exports = logger.default

Request id in logs

You can use context in logs by writing own formatter. However, there is one native functionality in the logger: adding request id to logs. Unique UUID identifier is generated for every request and then attached to every log. This way you can distinguish logs from many requests.

To use request ids you should just add the following code at start of initialization of your ExpressJS app:

const {
  logger: { useRid, getRid }
} = require('xpressjs')

// create `app` instance...

logger.useRid(app) // request id initialization

// next initialization...

In the example above you don't need additionally to initialize Context functionality as it is done internally. If you want to use Context in the same application for other purposes too, you can do it without having any conflict with request id functionality.

Get current request id

To get current request id you can use in code getRid() method as follows:

const {
  logger: { getRid }
} = require('xpressjs')

// create `app` instance...

logger.useRid(app) // request id initialization

// next initialization...

app.get('/', (req, res) => {
  res.json({ requestId: getRid(), message: 'Request is handled successfully' })
})

// other code...

getRid method must be used inside a middleware or request handler, but only after request id initialization.

Set custom request id

If you want to set custom request id without Express server, you can use setRid(rid) method.

const {
  logger: { setRid }
} = require('xpressjs')

setRid('custom-rid')

// now you can use logger, that will contain 'custom-rid' in logs...

Customize formatters

Default logger uses built-in formatters. There are two formatters provided by XpressJS library. If you overwrite format in options parameter of getLogger function, you have to use these formatters explicitly in code if you want them.

First is context formatter. It adds context data to the logs. If you want to use request ids in logs, you have to use formatters.context() formatter in new version of format option:

// /src/config/logger.js
const { format } = require('winston')
const {
  logger: { getLogger, formatters, labelValue }
} = require('xpressjs')

const { combine, timestamp, label } = format

module.exports = getLogger({
  format: combine(label({ label: labelValue }), timestamp(), formatters.context(), formatters.output)
})

formatters.output is responsible to determine logs format described above. If you don't specify it nor replace it by own formatter, logs will contain serialized data.

Unique timestamp

It may happen that many logs are registered in the same time. To distinguish them you can use unique timestamp that differs by at least 1 ms from others. To use it, you have to use uniqueTimestamp formatter in your logger configuration:

// /src/config/logger.js
const { format } = require('winston')
const {
  logger: { getLogger, formatters, labelValue }
} = require('xpressjs')

const { combine, timestamp, label } = format

module.exports = getLogger({
  format: combine(
    label({ label: labelValue }),
    timestamp(),
    formatters.uniqueTimestamp(),
    formatters.context(),
    formatters.output
  )
})

If you would like to use a preconfigured logger with unique timestamp, you may just import it from XpressJS:

// /src/config/logger.js
const { logger } = require('xpressjs')

module.exports = logger.uniqueTimestampLogger

Error handling

XpressJS provides a universal error handling mechanism. It covers functionality of throwing errors, mapping them to HTTP statuses, returning all errors to a requester in the same way and catching a situation when requested endpoint doesn't exist.

Reporting an error

In amy place of your application you can report a new error just using:

const { http } = require('xpressjs')

http.error.createError(404, 'Entity not found')

For controller, where you have access to ExpressJS, you can use next() function:

next(http.error.createError(404, 'Entity not found'))

In other parts of your application, you can throw result of the method as an exception:

throw http.error.createError(404, 'Entity not found')

XpressJS middleware will help you to catch this error and return to requester in a standardized way.

Handle errors

XpressJS error handler allows you to return all errors in your application in the same way. It does not matter if you throw it like an exception or use next() function. It can even display an HTTP 500 message if other exception occurred.

Structure of returned result contains two fields:

  • status: HTTP status code (integer),
  • message: human-readable message describing the error (string).

Example error response looks like:

{
  "status": 403,
  "message": "This operation is forbidden for you"
}

To handle errors with XpressJS you need to register its middleware after routing in the main app file:

const { statusHandler } = require('xpressjs')

// routing is registered here

app.use(statusHandler.handle())

handleErrors() method can take one argument which is a message returned if other server exception is thrown. If you don't provide it, default message for HTTP 500 will be used.

Report not existing endpoint

Using XpressJS you can easily report a situation, when a requested endpoint does not exist. To handle it, add the following code at the end of your routing:

const { handleNotFound } = require('xpressjs')

router.use(handleNotFound())

handleNotFound method can take one argument which is a message for the situation when requested endpoint is not found. If you don't provide it, default message for HTTP 404 will be used, eg.:

router.use(handleNotFound('API endpoint not found'))

Default messages for statuses

The most common status codes have default messages assigned. In a case you don't give your custom message, a default one will be used. You can customize them for your application. To do this, just run below code before routing in your application:

const { http } = require('xpressjs')

http.messages.setDefaultMessages({
  200: 'Created successfully'
  // other status codes with messages
})

These status messages are used both by createError and createSuccess methods.

Success responses

XpressJS can help with success responses. There are two common situation when it's usable.

Return a success message

Success message can be returned in the same way as error. It contains fields status, message and optional one: object.

Most common usage is to return a success message in a controller. It can be implemented as shown below.

function register(req, res, next) {
  const { email, password, name, surname } = req.body
  registerNewUser(email, password, name, surname) /// this is a model to register a new user
    .then(() => next(createSuccess(201, 'User is created successfully')))
    .catch((error) => next(error))
}

Code above will make response as below:

{
  "status": 201,
  "message": "User is created successfully"
}

Return related object

Sometimes you may need to return object related to the request, e.g. for new user frontend may need identifier created on the backend side.

Below is the same code but additionally object of new user is returned.

function register(req, res, next) {
  const { email, password, name, surname } = req.body
  registerNewUser(email, password, name, surname) /// this is a model to register a new user
    .then((user) => next(createSuccess(201, 'User is created successfully', user)))
    .catch((error) => next(error))
}

Assuming this request body:

{
  "email": "[email protected]",
  "password": "8A02XWTv0qHBDEz",
  "name": "John",
  "surname": "Smith"
}

code above will make response as below:

{
  "status": 201,
  "message": "User is created successfully",
  "object": {
    "id": 43,
    "email": "[email protected]",
    "name": "John",
    "surname": "Smith"
  }
}

Return list result

You can use XpressJS to return list of items with an information about all available pages. Response structure is shown below.

Structure of returned response contains two fields:

  • items: an array of objects containing items,
  • totalPages: number of all available pages (integer).

Example response looks like:

{
  "items": [],
  "totalPages": 1
}

Secure endpoints by JWT

JWT is a very common solution to protect API against unauthorized access. XpressJS helps you with basic activities related to JWT authentication.

Set up token secret

Token secret is a confidential value used to verify token. No one should know it. Library needs this value to confirm that received token is the right one.

You can set up this secret in two ways:

  1. set it in the environment variable: JWT_TOKEN_SECRET;
  2. use method from the library to set getter which returns secret:
const { jwt } = require('xpressjs')

const getJwtSecret = () => process.env.MY_OWN_SECRET
jwt.setUpTokenSecret(getJwtSecret)

Second method must be applied on the start of application - before token verification method is executed.

Generate JWT token

First part of authentication process is to generate a token and give it to a user. Inside the token you can put additional information like some public user's data.

Generating a token with payload data is done in this way:

const { jwt } = require('xpressjs')

// example payload
const userData = {
  email: '[email protected]'
}

// in an async function
const jwtToken = await jwt.signAccessToken(userData)

In the example above you will have JWT token in the jwtToken constant. Then you can return it to a user. This token will be used later to authenticate every request and must be attached in a header like this: Authentication:Bearer <JWT_TOKEN>

Verify request

Every request signed by the header: Authentication:Bearer <JWT_TOKEN> can be verified.

Having token in a token variable you can use the code below:

const { jwt } = require('xpressjs')

jwt
  .verifyAccessToken(token)
  .then((content) => {
    console.log(content.payload) // here is access to the payload set in the token
  })
  .catch((error) => {
    console.log(content.payload) // here you can handle error during veryfing the token
  })

Token verification as ExpressJS middleware

For simplification XpressJS provides ready-to-use middleware for ExpressJS. This middleware is created by function authenticate(options). You can just add a code like below in the main file:

const { jwt } = require('xpressjs')

app.use(jwt.authenticate())

Then all endpoints will be protected by JWT verification. Additionally, in every protected controller you can get access to payload

Endpoints whitelist

For security purposes it is a good practice to protect all endpoints as default. However, sometimes it may be needed to skip validation for some endpoints.

You can use options of middleware creator. It has a whitelist property. you can put therer an array of whitelisted endpoints. Every endpoint is described by two properties:

  • method - HTTP method, if not given, then all methods will be allowed;
  • path - whitelisted path; it can be both string and regular expression.

It's just enough to add this code in the main file:

app.use(
  jwt.authenticate({
    whitelist: [{ method: 'GET', path: /\/docs\/?.*/ }]
  })
)

Cryptography

XpressJS gives simple tools related to cryptography.

Hash a plain password

You should not keep plain passwords in database. It would be a very bad security issue. Instead of it you can save a hashed password. When in a login request you receive plain password, you hash it and compare to a result in a database.

XpressJS has a function to hash a given string: hashPassword(plainPassword). It takes one argument which is a plain password and returns hashed value.

An example to use this function for registration or log in a user can be like this:

const { crypto } = require('xpressjs')

// in a controller function (req, res, next):
const hashedPassword = crypto.hashPassword(req.body.password)

In password field is sent in a JSON body of the request, in hashedPassword you will have hashed value of sent password.

Generate random token

Sometimes you may need to generate a random alphanumeric token, eg. to create access tokens. In this case you can use generateToken(size=20) function. It takes size of generated token which is set to 20 as default. Function returns a generated random token.

Example how to use it in a code:

const { crypto } = require('xpressjs')

const token = crypto.generateToken(15)

token variable will contain a string of 15 characters.

Handling and parsing filters

XpressJS can help with handling query parameters. It offers to take only defined parameters and to parse them if needed. The library has filters.get(rawFilters, definition) method which takes two parameters:

  • rawFilters - it is an object having all available query parameters, in ExpressJS it's usually req.query;
  • definition - it is an object with definitions of all parameters available in the application.

Definition object has properties having the same names as query parameters. Values are objects which have two fields:

  • default - required field with default value taken if nothing is passed in a request;
  • parser - method which takes raw value (string taken from query string) and can return parsed value.

Default value is not parsed using parser function.

Example usage of this method is shown below:

const { filters } = require('xpressjs')

const parsedFilters = filters.get(req.query, {
  // comma-separated category identifiers
  categories: {
    default: [],
    parser: (raw) => raw?.split(',').map((categoryId) => +categoryId) ?? []
  },
  // published flag can take stringified boolean values: 'true'/'false'
  published: {
    default: true,
    parser: (raw) => raw === 'true'
  },
  // identifier of author; if not given, then no filter can be applied to database query
  author: {
    default: null
  }
})

parsedFilters contain an object with three filters taken from query and parsed if parser method is defined for a filter:

  • categories,
  • published,
  • author.

.env modification

.env files are often used to set environment variables. Usually in a project these variables are just read, however sometimes you may need to modify them.

This feature can be used when besides API server you write some console commands. You may want eg. to generate token secret for JWT. This operation is shown in the example below:

Watch-out! Please note that XpressJS doesn't have any mechanism to reload your application when environment variables changed in .env file. You must do it on your own.

Add or modify environment variable

To add a set of environment variables to env file you can just use updateEnv(parameters) function. It takes one parameter which is a configuration of variables modification operation. This object may contain three properties:

  • filePath - path to environment file; as default value is used .env file;
  • data - mandatory object with environment variables to be added or modified;
  • override - a flag informing if changing values of already existing variables is allowed.

Below is an example how to use this function in a real code.

const { env, crypto } = require('xpressjs')

env.modifier.updateEnv({
  filePath: '.env.local',
  data: {
    TOKEN_SECRET: crypto.generateToken(64)
  },
  override: true
})

This code will add or refresh TOKEN_SECRET environment variable located in .env.local file.

Remove existing variable from a file

To remove a set of environment variables you can use deleteValues(fieldsToDelete, filePath) function. It takes two parameters:

  • data - environment variables which should be removed;
  • filePath - path to environment file; as default value is used .env file.

Below is an example how to use this function in a real code.

const { env, crypto } = require('xpressjs')

env.modifier.deleteValues(['TOKEN_SECRET'], '.env.local')

This code will remove TOKEN_SECRET environment variable from .env.local file.

Request validation

Other common functionalities needed in many projects is validation of requests. Most of all endpoints accept only a specific set of parameters both in query string and in body.

XpressJS comes with middleware creators which produces these validators based on passed configuration. It uses Zod as a tool to define expected schema.

Below is an example of schema definition in Zod:

const { z } = require('zod')

const expectedObject = z.object({
  name: z.string(),
  age: z.number().optional().gte(18),
  quantity: z.number().multipleOf(3),
  product: z.enum(['Apple', 'Banana'])
})

In this example there is a definition of object. It has 4 properties:

  • name - required string;
  • age - optional number, but must be equal or greater than 18;
  • quantity - required number being a multiplication of 3, eg. 3, 6, 9, etc.;
  • product - required string, one of two options: Apple or Banana.

Object definition like this can be used for both query parameters and request's body validation.

As a better practice of code organization, you can move schema definitions to separate files and then just import it for validation.

Query parameters validator

The library provides method validateQuery(expectedQuery, errorHandler). It takes two arguments. expectedQuery contains Zod schema definition. It allows to create even very extensive schemas to describe even complicated requirements. As validateQuery function verifies the whole req.query object, it expects to take zod.object() in expectedQuery parameter. Second argument is a handler for errors if received data doesn't match expected schema.

Below is an example of query parameters validation for GET /users endpoint.

const express = require('express')
const { validator } = require('xpressjs')
const { z } = require('zod')

const expectedQuery = z.object({
  name: z.string(),
  age: z.number().optional().gte(18)
})

const app = express()

app.get('/users', validator.validateQuery(expectedQuery), (req, res, next) => {
  // body of a controller
})

In this example two query parameters are validated. First is name. It's expected to be a mandatory string. Second is age - an optional number. More information about schema definition in Zod you can find its documentation.

The same job can be done when using router:

const router = express.Router()

router.get('/users', validator.validateQuery(expectedQuery), (req, res, next) => {
  // body of a controller
})

Request body validator

The same job as for query parameters can be done with request body. Usually it's an object with properties. You can define for it a Zod schema and use for validation.

XpressJS comes with validateBody(expectedQuery, errorHandler) function. It takes the same arguments as validateQuery(). First argument is Zod schema definition of request body. Second is a handler for errors if received data doesn't match expected schema.

Below is an example how to use validateBody() function.

const express = require('express')
const { validator } = require('xpressjs')
const { z } = require('zod')

const expectedQuery = z.object({
  name: z.string(),
  age: z.number().optional().gte(18)
})

const app = express()

app.post('/users', validator.validateBody(expectedQuery), (req, res, next) => {
  // body of a controller
})

In this example request body can be an object with two properties. First is name. It's expected to be a mandatory string. Second is age - an optional number. More information about schema definition in Zod you can find its documentation.

The same job can be done when using router:

const router = express.Router()

router.post('/users', validator.validateBody(expectedQuery), (req, res, next) => {
  // body of a controller
})

Handling requests with invalid data

As a default behavior, request validators force ExpressJS application to return HTTP 400 error. They use error response creator createError() with messages:

  • validateQuery: Query parameters: ${invalidFields} are invalid;
  • validateBody: Body fields: ${invalidFields} are invalid.

invalidFields is a string having all fields marked as invalid, separated by , comma sign.

You can do nothing to have default behavior. However, if you want to do custom action, you can use errorHandler parameter passed to all functions. As default it is undefined.

Error handler can be a function which takes 4 arguments:

  • invalidFields - string with invalid fields, separated by , comma sign;
  • req - taken from ExpressJS;
  • res - taken from ExpressJS;
  • next - taken from ExpressJS.

An example of custom handler is shown below.

const express = require('express')
const { validator } = require('xpressjs')
const { z } = require('zod')

const logger = require('./config/logger')

const expectedQuery = z.object({
  name: z.string(),
  age: z.number().optional().gte(18)
})

const errorHandler = (invalidFields, req, res, next) => {
  const message = `Invalid fields detected in a request: ${invalidFields}`
  logger.error(message)
  next(next(createError(400, message)))
}

const app = express()

app.post('/users', validator.validateBody(expectedQuery, errorHandler), (req, res, next) => {
  // body of a controller
})

This example defines a schema and error handler. Later validator uses them and in a case if request has invalid data, such fact is logged and passed to next handlers, eg. middleware from XpressJS which return it to a requester.

Environment validation

Usually ExpressJS application uses some environment variables. They must be defined before running the application and inside are available as properties of process.env object. It may happen that for some reason not all required environment variables are available in the application. It may cause issues, eg. not authorized internal requests to other services when authentication keys are stored in env variables.

To prevent an application even to run without required variables, XpressJS comes with environment validator. It's a method checkRequiredVars(requiredVariables). It takes one argument which is an array of required variable names. Function return a Promise. You can catch an error and decide what you want to do, eg. exit the application with logs.

In then part you can handle correct situation if all required variables are available. You can then start ExpressJS server. This handler does not take any arguments.

Situation, if any required variable is not available, will be handled by catch. Passed function can take one argument which is an object having two properties:

  • message - already prepared error message; it is: Required environment variables: ${notAvailableVariables} are not available where notAvailableVariables is a variable with all non-available env variable names, separated by , comma sign;
  • variables - an array of lacking required environment variable names.

Watch-out! Your application must read environment variables before they are validated. You can use dotenv package for this purpose.

Example code below shows how to prevent an application to run if not all required environment variables are available.

// import environment variables to the application at start
require('dotenv').config()

const { env } = require('xpressjs')

const logger = require('./config/logger')

// other parts of main file, including `app` constant initialization

env.validator
  .checkRequiredVars(['JWT_SECRET_TOKEN', 'EXTERNAL_API_TOKEN'])
  .then(() => {
    app.listen(3000, () => {
      logger.info('Server is listening on port 3000')
    })
  })
  .catch((error) => {
    // log information about not available variables
    logger.error(error.message)
    // exit the application to prevent issues related to lacking variables
    process.exit(1)
  })

This example requires dotenv package to be installed.

Synchronous async operations

Asynchronous operations are very common in NodeJS applications. Standard way to handle both success and error is a then/catch construction, similar to try/catch from non-promised approach. However sometimes you need only to handle result or error and do something with it.

XpressJS comes with simple method to handle asynchronous promises in a more synchronous way. It is offered by sync() function which takes Promise object and return object with two properties:

  • error - error object if reported, otherwise null value;
  • result - result of a promise if everything happened correctly, otherwise null value.

sync() function handles both cases: success and error. It allows to have the same code for both situations.

Below is an example of how to use sync() function. The code contains getContent function which handles errors and logs them. If everything is correct, it returns response data.

const axios = require('axios')
const { sync } = require('xpressjs')

/**
 * @param {string} url
 * @returns {Promise<?string>}
 */
async function getContent(url) {
  const { error, result } = await sync(axios.get(url))
  if (error) {
    console.log(`Error occured while fetching content: ${error.message}`)
  }
  return result.data
}

Health check

It is a good practice to prepare an endpoint to verify availability of the whole API. XpressJS offers a very simple way to implement basic health check controller under GET / path.

To add health check controller you just need to import healthcheck function from XpressJS and execute healthcheck.useHealthCheck in your main file. An example of basic setup is below:

const express = require('express')
const { healthcheck } = require('xpressjs')

const app = express()
healthcheck.useHealthCheck(app)

Code above health check endpoint of the application. This endpoint is available under the path: GET / and returns result with application/json type. Result looks like this:

{
  "app": {
    "name": "your-app-name",
    "version": "1.2.3"
  },
  "date": "2023-11-10T18:35:21.145Z",
  "result": "success"
}

Fields mean:

  • app.name - application name taken from name field in package.json file;
  • app.version - application version taken from version field in package.json file;
  • date - date and time of calling the endpoint;
  • result - constant success string, always the same.

Customize health check endpoint

Generally health check endpoint is /. However, you may want to change it sometimes, eg. when your whole application works under /app or /api prefix.

const express = require('express')
const { healthcheck } = require('xpressjs')

const app = express()
healthcheck.useHealthCheck(app, '/api')

Customize health check controller

You may want to do additional checks when health check controller is called. You can do it just by passing second argument to useHealthCheck function. If you pass a value there, it must be a function taking three arguments like every Express controller: req, res and next. You don't need to use these parameters.

Callback function should return nothing or an object. If object is returned, it will be merged with default health check result and returned as a response.

Below is an example of custom callback which checks availability of additional services.

const express = require('express')
const { healthcheck } = require('xpressjs')

const app = express()

function healthCheckCallback(req, res, next) {
  return {
    availability: {
      db: true,
      cdn: true,
      sap: false
    }
  }
}
healthcheck.useHealthCheck(app, '/', healthCheckCallback)

When you call GET / endpoint, you get result like this:

{
  "app": {
    "name": "your-app-name",
    "version": "1.2.3"
  },
  "date": "2023-11-10T18:35:21.145Z",
  "result": "success",
  "availability": {
    "db": true,
    "cdn": true,
    "sap": false
  }
}

Healthcheck callback can be synchronous or asynchronous function. XpressJS recognizes its type and works with both.

Health check controller

You may want to implement health check endpoint in a different way than proposed by XpressJS library. Besides ready-to-use solution it provides to you a simple controller which you can just connect to any path you need. To use it you need to import healthcheck.controller like it's shown below.

const express = require('express')
const { healthcheck } = require('xpressjs')

const app = express()
app.get('/healthcheck', healthcheck.controller)

It will make health check controller available under GET /healthcheck path.

Sleep

XpressJS library offers a simple sleep() function to stop executing an application and wait for a concrete time before continuing. It can be used eg. to ensure that application will not fail because of rate limit of external APIs.

sleep function takes one parameter which is number of milliseconds determining length of the break. An example code is shown below.

const { sleep } = require('xpressjs')

async function updateResources(resources) {
  for (const resource of resources) {
    await updateResource(resource)
    await sleep(200)
  }
}

In the example updateResources function executes updateResource function for every passed resource. updateResource uses external API which permits to be called only 6 times per a second. sleep function will stop execution for 200ms after updating every resource to make sure that all requests will meet the rate limit.

Resolve Promises with delay

Usually you want the application to execute the code as fast as possible. However, because of some restrictions like external API's rate limit you have to wait between next calls. You may use sleep() function from XpressJS, but the library comes with next functionality designed specially for asynchronous operations.

API calls are always realized as Promises. If needed, you may use delayed function. It takes two parameters:

  • p - this is Promise object which should be resolved after a time;
  • delay - number of milliseconds that must elapse before Promise can be resolved.

Example usage:

const { delayed } = require('xpressjs')

async function getData(pageNo) {
  // implementation of fetching paginated data from external API
}

const totalPages = 10
const pages = Array.apply(null, { length: totalPages + 1 })
  .map(Number.call, Number)
  .slice(1)

let allData = []
for (const pageNo = 1; pageNo <= totalPages; pageNo++) {
  const pageData = await delayed(getData(pageNo), 500) // delay 500ms before resolving the given promise
  allData = [...allData, ...pageData]
}

Fetch API

Node.js does not have implemented Fetch API natively. You can use node-fetch package for this purpose. However, the package is ESM-only module. If you want to use Fetch API in a project where you use CommonJS modules, XpressJS comes with a solution.

You can just import fetch like in the example below:

const { fetch } = require('xpressjs')

const response = await fetch('https://example.com')

Swagger documentation

XpressJS offers a simple way to generate Swagger documentation for your API. It can both generate a Swagger file and serve documentation page under a specific path.

Under the hood, it uses swagger-autogen package. You can find more information about it here.

Generate Swagger file

To generate Swagger file you need to import swagger object from XpressJS and use generate method. It takes three parameters:

  • outputPath - path where the file should be saved;
  • swaggerDefinition - object with Swagger definition, prepared in accordance with the OpenApi standard;
  • endpointsFiles - array of paths to files with endpoints definitions.

Assuming that you have routes defined in the routes.js file and swagger definition is in the swagger/definition.js, you can generate Swagger file like this:

// swagger.js
const {
  swagger: { generate }
} = require('xpressjs')
const swaggerDefinition = require('./swagger/definition')

generate('docs/OAS3.json', swaggerDefinition, ['./routes.js']).then(() => console.log('Swagger file generated'))

If you want to automate this process, you can add a script to your package.json file:

{
  "scripts": {
    "generate-swagger": "node swagger.js"
  }
}

Then you can run it with npm run generate-swagger to generate docs/OAS3.json with the newest version of Swagger documentation.

Additional endpoints

Some endpoints may be added automatically to Swagger definition. These are:

  • /healthcheck - if you use XpressJS health check controller;
  • /docs - if you use XpressJS Swagger documentation controller.

Swagger provider used for serving documentation under a specific path, described below, adds these endpoints automatically. However, generator doesn't have such knowledge. That's why you have to specify all endpoints files in the endpointsFiles parameter. It's an optional parameter passed to generate method. If you don't pass it, these endpoints will not be included in the Swagger file.

// swagger.js
const {
  swagger: { generate }
} = require('xpressjs')
const swaggerDefinition = require('./swagger/definition')

generate('docs/OAS3.json', swaggerDefinition, ['./routes.js'], { healthcheck: '/', docs: '/docs' }).then(() =>
  console.log('Swagger file generated')
)

Serve Swagger documentation under a specific path

To serve Swagger documentation page you need to prepare an endpoint with Swagger documentation and then to add it to the application.

To do first step, you have to import swagger object from XpressJS and use getSwaggerEndpoint method. It takes three parameters:

  • swaggerDefinition - object with Swagger definition, prepared in accordance with the OpenApi standard;
  • endpointsFiles - array of paths to files with endpoints definitions;
  • endpointPath - an optional path to the endpoint with Swagger documentation; as default value is used /docs.

getSwaggerEndpoint is asynchronous function and returns handler that can be used in a second step - to add documentation endpoint to the application. You can do it by using useSwagger method from swagger object imported from XpressJS library. The method takes two parameters:

  • app - ExpressJS application instance;
  • handler - handler returned by getSwaggerEndpoint method.

Watch-out! Starting the server application should be done after adding the Swagger documentation endpoint within then block after getSwaggerEndpoint method, as shown in the example below. useSwagger method should be called as the first command there.

Watch-out! If you want to use handleNotFound and statusHandler from XpressJS library, you should put them within then block after getSwaggerEndpoint method, before starting the application.

// create app instance here...

const {
  swagger: { getSwaggerEndpoint, useSwagger }
} = require('xpressjs')
const logger = require('./config/logger')
const swaggerDefinition = require('./swagger/definition')

getSwaggerEndpoint(swaggerDefinition, ['./routes.js'])
  .then((handler) => {
    useSwagger(app, handler)
    app.use(handleNotFound('API endpoint not found'))
    app.use(statusHandler.handle())

    const port = process.env.PORT || 3000
    app.listen(port, () => {
      logger.info(`Server is listening on port ${port}`)
    })
  })
  .catch((errorMessage) => logger.error(errorMessage))

To use a path different than /docs you can pass it as a third parameter to getSwaggerEndpoint method:

getSwaggerEndpoint(swaggerDefinition, ['./routes.js'], '/api-docs')

Documentation content types

When you go to /docs path in your application, you will see Swagger documentation page. If you attach Accept: application/json header to the request or format=json as a query parameter, you will get JSON file with Swagger definition.

Swagger definition

Swagger definition is a JSON object with OpenApi standard. It should contain all necessary information about your API. Patches are generated automatically so don't need to be included here. You may put

Below is an example of Swagger definition. The example contains JS file for better adaptability. You may use JSON file as well.

module.exports = {
  info: {
    title: 'API application title',
    version: '1.0.0',
    description: 'API application description',
    contact: {
      name: 'App author',
      email: '[email protected]'
    }
  },
  servers: [
    {
      url: 'http://localhost:3000'
    }
  ],
  components: {
    '@schemas': {
      ...require('./Healthcheck.schema.js'),
      ...require('./Response.schema.js'),
      ...require('./ErrorResponse.schema.js')
    }
  }
}

In this example models are extracted to separate files. It's a good practice to keep Swagger definition file as clean as possible.

Models

Swagger definition may contain models. They are used to describe structure of request and response bodies. Models are defined in the components object under @schemas key. Each model is a separate object with properties describing fields.

XpressJS provides prototypes of models for common use cases. You can find them in the models directory in the library. Below is an example of Healthcheck model definition.

const {
  swagger: { models },
  utils: { deepMerge }
} = require('xpressjs')

module.exports = {
  Healthcheck: deepMerge(models.Healthcheck, {
    properties: {
      services: {
        type: 'object',
        description: 'Status of external services',
        properties: {
          service1: {
            type: 'boolean',
            description: 'Status of Service1 service',
            example: true
          },
          service2: {
            type: 'boolean',
            description: 'Status of Service2 service',
            example: false
          }
        }
      }
    }
  })
}

Watch-out! Two models: Healthcheck and ErrorResponse are defined by XpressJS and automatically attached to Swagger definition. So both will be available even if you don't define them in your configuration. As shown above, you can redefine them and overwrite.

Models defined in the library

XpressJS defines three models:

  • Healthcheck - model for health check response;
  • SuccessResponse - model for successful response;
  • ErrorResponse - model for error response.

All are available in the models object imported from the library.

Healthcheck model:

  • app - object with information about the application;
    • name - name of the application;
    • version - version of the application.
  • date - date and time of the health check.
  • result - constant string success.

SuccessResponse model:

  • status - HTTP status code;
  • message - message about the success;
  • object - optional object with additional data; it may be extended by a developer in an application.

ErrorResponse model:

  • status - HTTP status code;
  • message - message about the error;
  • error - optional object with additional data; it may be extended by a developer in an application.

Utils

XpressJS comes with some helper functions to simplify your common use cases. All these utility functions are available under utils key in the library.

const { utils } = require('xpressjs')

Deep merge

Sometimes you may need to merge two objects together. You can do this by using deepMerge function and pass both objects as parameters. Below is an example.

const {
  utils: { deepMerge }
} = require('xpressjs')

const obj1 = {
  a: 1,
  b: {
    c: 2,
    d: 3
  }
}
const obj2 = {
  b: {
    c: 4
  },
  e: 5
}

const merged = deepMerge(obj1, obj2)
/*
Result:
{
  a: 1,
  b: {
    c: 4,
    d: 3
  },
  e: 5
}
 */

Chunk array

If you have an array, and you want to split it into smaller arrays, you can use chunkArray function. It takes two parameters: an array to split and a number of elements in a chunk. Below is an example.

const {
  utils: { chunkArray }
} = require('xpressjs')

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const chunked = chunkArray(array, 3)
/*
Result:
[
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
  [10]
]
 */

Filter unique values

If you have an array with duplicated values, and you want to get only unique values, you can use unique function. It takes two parameters:

  • arr - an array with values;
  • fn - an optional function to determine uniqueness of values.

If an array contains simple values, there is no need to pass fn, because items are considered as values to determine uniqueness. If you have an array with objects, you can pass a function to get unique value for every item.

const {
  utils: { unique }
} = require('xpressjs')

const array = [1, 2, 3, 2, 1, 4, 5, 6, 5, 7]
const uniqueValues = unique(array)

// Result: [1, 2, 3, 4, 5, 6, 7]

Filter non-empty values

If you have an array containing empty values, and you want to get only non-empty items, you can use nonEmpty function. It takes one parameter: an array with values.

const {
  utils: { nonEmpty }
} = require('xpressjs')

const array = [1, '', 3, null, 5, undefined, 7]
const nonEmptyValues = nonEmpty(array)

// Result: [1, 3, 5, 7]