Express User Manager

Michael Orji

Backend Engineer
Frontend Engineer
Fullstack Engineer
MongoDB
MySQL
Node.js

Express User Manager

A user management and authentication library for Express apps.
It automatically creates and adds the following API endpoints to an Express app:
user registration
user login
user logout
user retrieval
users listing
user searching
user data update
user account deletion
Additional features include:
customizable API endpoints
support for multiple database engines and data-storage mechanisms
customization of the minimum and maximum length of passwords
specification of non-secure passwords that should not be allowed for use as passwords
New in V3.0.0: Support for Hooks

Table of Contents

Installation

npm install --save express-user-manager

Quick start

const express = require('express'); const userManager = require('express-user-manager'); const app = express(); /** * Setup the datastore using any of the currently supported database adapters: * - mongoose: for MongoDB * - sequelize: for any of the other supported database engines: * MySQL | MariaDB | SQLite | Microsoft SQL Server | Postgres | In-memory DB * (See the section on "Built-in data stores" for supported database engines) */ const dbAdapter = 'mongoose'; // OR 'sequelize' const store = userManager.getDbAdapter(dbAdapter); // Bind the routes under [apiMountPoint] (default: ***/api/users***): userManager.listen(expressApp, apiMountPoint = '/api/users', customRoutes = {}); (async function() { const server = http.createServer(app); // Establish a connection to the data store // Ensure the db is connected before binding the server to the port await store.connect({ host: DB_HOST, // optional, default: 'localhost' port: DB_PORT, // optional user: DB_USERNAME, // optional pass: DB_PASSWORD, // optional engine: DB_ENGINE, // optional if the adapter is "mongoose" or if the value is "memory" and the adapter is "sequelize"; required otherwise dbName: DB_DBNAME, // optional, default: 'users' storagePath: DB_STORAGE_PATH, // optional, required if "engine" is set to "sqlite" debug: DB_DEBUG, // optional, default: false exitOnFail: EXIT_ON_DB_CONNECT_FAIL // optional, default: true }); // Proceed with normal server initialization tasks server.listen(PORT); server.on('error', onError); server.on('listening', onListening); })(); // Optionally listen for and handle events // (See the Emitted events section for more) userManager.on(EVENT_NAME, function(data) { // do something with data });
Quick notes
The expressApp parameter has the following constraints:
The apiMountPoint parameter allows you to specify the base API route. Every request to the API will be relative to this base route. The default is /api/users.
The customRoutes parameter is an object that allows customization of the routes.
If your expressApp has its own custom routing in place, make sure to call userManager.listen(expressApp) before setting up your app's custom 404 handler.

The init method

The init method provides a shortcut way to perform the setup and initialization steps above.
It is an async function that runs setup and initialization tasks, connects to the database, then starts listening for requests, all in a single step: await init(app, options);.
It takes two parameters:
an express.js app as the first argument
an object (with the same signature as the object passed to the config method) as the second argument.

Configuration

express-user-manager can be configured in several ways:
using environment variables (See the Environment variables section)
using the config method (See The config method)
passing configuration options as the second parameter to the init(app, options) method
using a combination of environment variables and the config method

Environment variables

NODE_ENV (string): The environment in which the app is running: development, production, staging, test, etc.
API_MOUNT_POINT (string): The route under which to listen for API requests, default is: /api/users
PORT: The port on which the server is running (or should run, if using as a stand-alone server)
DB_ENGINE: The database engine to use. Should be one of the supported databases. (See Built-in data stores)
DB_ADAPTER: The adapter to use. Set it to mongoose if using MongoDB; Set it to sequelize otherwise.
DB_STORAGE_PATH: Define this only when the DB_ENGINE is set to sqlite.
DB_HOST: The database host
DB_USERNAME: The database user
DB_PASSWORD: The database user's password
DB_DBNAME: The name of the database
DB_PORT: The port on which the database is running
DB_DEBUG: Set to true or a non-zero integer to display debug output for the database.
EXIT_ON_DB_CONNECT_FAIL: Set to true or a non-zero integer if the app should exit if it is unable to establish a connection to the database.
SESSION_SECRET (string)
AUTH_TOKEN_SECRET (string)
AUTH_TOKEN_EXPIRY (number): Authorization token expiry (in seconds)
PASSWORD_MIN_LENGTH (number)
PASSWORD_MAX_LENGTH (number)
DISALLOWED_PASSWORDS: (array): A comma-separated list of weak/non-secure passwords that should not allowed to be used as passwords
Note: express-user-manager uses the dotenv package, so a quick and easy way to define the above variables is to create a .env file at the root of your project directory, and add them to the file and they will automatically be picked up. Sample .env file

The config method

As stated earlier in the Configuration section, one of the ways you can configure express-user-manager is by using the config method.
This method provides an alternate way to pass configuration values to express-user-manager* if you haven't done (or are unable to do) so via environment variables.
Below is an example touching on every setting:
apiMountPoint: (string) specifies the users API routes base route
password: (object) for configuring minimum and maximum password length, as well as disallowed passwords
routes: (object) for setting up custom API endpoints
db: (object) encapsulating database connection information
security: (object) for configuring session and authorization tokens and expiry
const express = require('express'); const userManager = require('express-user-manager'); const app = express(); const dbAdapter = 'mongoose'; // OR 'sequelize' // Call config(options) to configure the app userManager.config({ apiMountPoint: {string}, // The base route under which to listen for API requests password: { // {object} for password configuration minLength: {number}, // minimum length of user passwords, default: 6, maxLength: {number}, // maximum length of user passwords, default: 20 disallowed: {string | array}, // comma-separated string or array of strings considered weak/non-secure passwords }, routes: { // {object} for configuring custom routes, with members list: {string}, // specifies the path for getting users listing search: {string}, // specifies the path for searching users getUser: {string}, // specifies the path for getting a user's details via their username, a /:{username} is appended to this path signup: {string}, // specifies the user registration path login: {string}, // specifies user authentication path, logout: {string}, // defines the logout path updateUser: {string}, // specifies the path for updating a user's data deleteUser: {string} // specifies the path for deleting a user, a /:{userId} is appended to this path }, db: { // {object} for configuring the database connection adapter: {string}, // the adapter to use. valid values include 'mongoose', 'sequelize' host: {mixed}, // database host port: {number}, // database port user: {string}, // database user pass: {string}, // database user's password engine: {string}, // the database engine, when the adapter is set to "sequelize". values: 'memory', 'mariadb', 'mssql', 'mysql', 'postgres', 'sqlite' dbName: {string}, // name of the database to connect to storagePath: {string}, // the database storage path, only valid when "engine" is "sqlite". combined with `dbName`: `${storagePath}/${dbName}.sqlite` debug: {boolean}, // a value of true outputs database debug info exitOnFail: {boolean}, // set to true to kill the Node process if database connection fails }, security: { // {object} for configuring security sessionSecret: {string}, // a key for encrypting the session authTokenSecret: {string}, // a key for signing the authorization token authTokenExpiry: {number}, // the expiry time of the authorization token (in seconds), example: 60 * 60 * 24 } }); async(() => { /** * The dbAdapter argument is not required if it is either: * - specified in the db section of the call to config or as * - set using the DB_ADAPTER environment variable */ const store = userManager.getDbAdapter([dbAdapter]); /** * The connectionOptions are not required if the values: * - are already specified in the db section of the call to config * - are set using the DB_* environment variables */ await store.connect([connectionOptions]); // If the dbAdapter and connection values are already specified // via config or via environment variables, // then the above two calls can be tersely combined in a single call: // await userManager.getDbAdapter().connect(); }); // Bind request listeners userManager.listen(expressApp);
Notes on configuration settings:
Any of the above settings can be omitted in the call to config if they are already defined using environment variables. The exception to this is routes, which cannot be set via an environment variable. However, you can set it using the third parameter to the call to listen(app, apiMountPoint, routes).
If a setting is defined as an environment variable and also set in the call to config, the value set in config will take precedence and be used instead.
The apiMountPoint can be set (in increasing order of precedence):

Specifying custom API endpoints

To customize the request paths, either:
pass a routes property with the API endpoints to config: userManager.config({ routes: customApiEndpoints }); userManager.listen(expressApp, apiMountPoint);
pass an object with the API endpoints as the last parameter to userManager.listen: userManager.listen(expressApp, apiMountPoint, customApiEndpoints)
Below is the default definition of the API Endpoints, which can be modified for your custom routes:
const customApiEndpoints = { list : '/', // Resolves to [apiMountPoint]/ search : '/search', // Resolves to [apiMountPoint]/search getUser : '/user', // Resolves to [apiMountPoint]/user/:username signup : '/', // Resolves to [apiMountPoint]/ login : '/login', // Resolves to [apiMountPoint]/login logout : '/logout', // Resolves to [apiMountPoint]/logout updateUser : '/', // Resolves to [apiMountPoint]/ deleteUser : '/user', // Resolves to [apiMountPoint]/user/:userId };

API endpoints object properties

As seen above, the default object has a number of properties, each corresponding to a request path:
list : Specifies the path to get users listing
search : Specifies the path to search for users
getUser : Specifies the path to get a user by username (a /:username is automatically appended to the end of this route)
signup : Specifies the path for creating (i.e., registering) a new user
login : Specifies the path for logging in a user (an authorization key is returned on successful login)
logout : Specifies the path to log out a user
updateUser: Specifies the path for updating user information
deleteUser : Specifies the path for deleting user by id (a /:userId is automatically appended to the end of this route)

Built-in middlewares

The userManager module provides some middlewares. You can get them by calling: userManager.get('middlewares');. This will return an object with the following middlewares:
authorized: For protected resources. It ensures an access/authorization token is sent along with the request using the Authorization header.
loadUser: Loads the current user (identified by username) into the request, to that it is available to every other middleware in the middleware chain. The username is sent as part of the request parameters (request.params)
loggedIn: Ensures a user is logged in before they can perform the requested action.
notLoggedIn Ensures that the target action is available only to users who are not logged in. For example, registration and login should (normally) not be permissible if the current user is already logged in.
restrictUserToSelf: Constrains a user to performing certain actions only on their own account.

Hooks

Hooks are a mechanism to allow you hook into different parts of the application's lifecycle.

Available hooks

Request hooks: allow you define and register custom request middlewares. They give you the ability to modify the request any way you see fit.
Response hooks: let you define and register custom response-modifying functions.
You can register a request or response hook for a single route, for multiple routes, or for all routes.

Registering request and response hooks

To register a request or response hook:
define a middleware, which is just a fancy word for a function that takes three parameters: req, res, next.
decide on which route (or routes) the middleware should be registered for: single-route, multi-route, global. The route(s) should correspond to a key or keys in the API endpoints configuration object.
Register the hook:

Registering hooks: examples

Register a request hook for every route: userManager.addRequestHook('*', function(req, res, next) { // Do something interesting here, with the request or the response. req.accessTime = Date.now(); // Call next to pass control onto the next middleware function next(); });
Register a response hook for the signup route: userManager.addResponseHook('signup', function(req, res, next) { // You can for example set/append custom response headers: res.append('Access-Control-Allow-Headers', '<CUSTOM_HEADER>'); });
Register a response hook for multiple (login and signup) routes: userManager.addResponseHook(['login', 'signup'], function(req, res, next) { // Do something tangible res.body.data.authenticated = true; });
Register a list of request hooks along with their corresponding function identifiers: userManager.addRequestHook(['login', 'signup'], ['loginFnId', 'signupFnId'])
Register multiple response hooks for a single (signup) route: userManager.addResponseHook('signup', [callback1, callback2, ...]);

Unregistering request and response hooks

You also have the ability to unregister a hook when the hook is no longer needed. And you can unregister hooks globally, for only some routes, or for a single route.
To unregister a request hook, call userManager.removeRequestHook(route [, callback]).
To unregister a response hook, call userManager.removeResponseHook(route, [callback]);

Unregistering hooks: examples

Unregister every request hook:
Unregister every response hook for the user signup route:
Unregister only a request callback hook for the signup route:
Unregister a list of request hooks along with their corresponding function identifiers:
Unregister every response hook for the passed routes list:
Unregister multiple request hooks for a single (login) route:

Built-in data stores (database adapters and engines)

In-memory (Adapter: sequelize)
MariaDB (Adapter: sequelize, Engine: mariadb)
Microsoft SQL Server (Adapter: sequelize, Engine: mssql)
MongoDB (Adapter: mongoose)
MysQL (Adapter: sequelize, Engine: mysql)
Postgres (Adapter: sequelize, Engine: postgres)
SQLite (Adapter: sequelize, Engine: sqlite)

Emitted events

Events emitted by the database

dbConnection
dbDisconnect
createUser

Events emitted by request handlers

signupError
signupSuccess
loginError
loginSuccess
logoutSuccess
getUsersError
getUsersSuccess
searchUsersError
searchUsersSuccess
getUserSuccess
updateUserError
updateUserSuccess
deleteUserError
deleteUserSuccess

Events emitted by middlewares

actionNotPermittedError
authorizationError
authenticationError

Password constraints

minimum length of PASSWORD_MIN_LENGTH environment variable
maximum length of PASSWORD_MAX_LENGTH environment variable
must contain at least one number
must contain at least an uppercase character
must contain at least a lowercase character
must not be among the values specified in the DISALLOWED_PASSWORDS environment variable

Usage as a stand-alone server

The package comes with a built-in express server that allows you run it as a stand-alone server.
To run it as a stand-alone server, do the following:
Ensure you have a server running for your preferred database engine. (See Setting up test databases for some examples)
Define the environment variables listed in the Environment variables section.
start the server, using one of these two methods:
Note: The built-in server runs using the default route/path settings. That means:
it runs under the /api/users base route (mount point).
it uses the default request paths. (See the section on Requests and responses)

Requests and responses

Every route below is assumed to begin (i.e., prefixed) with the base API route (or mount point). The default base API route is /api/users.
Create user
Get user details by username
Retrieve list of users
Search for users
Login
Logout
Update user data
Delete user by ID

Contributing

CHANGELOG

License

Author

Partner With Michael
View Services

More Projects by Michael