{"id":15296005,"url":"https://github.com/simplymichael/express-user-manager","last_synced_at":"2025-04-13T19:51:53.913Z","repository":{"id":143878995,"uuid":"314534809","full_name":"simplymichael/express-user-manager","owner":"simplymichael","description":"A user management and authentication library for Express apps. Automatically creates and adds relevant (customizable) API endpoints to an Express app.","archived":false,"fork":false,"pushed_at":"2022-09-26T13:40:07.000Z","size":1299,"stargazers_count":19,"open_issues_count":5,"forks_count":6,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-27T10:37:41.025Z","etag":null,"topics":["authentication","authorization","express","expressjs","login","login-system","node","nodejs","registration","user","user-management","user-management-system"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/simplymichael.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-20T11:30:54.000Z","updated_at":"2024-12-29T08:23:03.000Z","dependencies_parsed_at":null,"dependency_job_id":"63f5f403-1bc1-4115-9ed7-bf955f2ddeaa","html_url":"https://github.com/simplymichael/express-user-manager","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplymichael%2Fexpress-user-manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplymichael%2Fexpress-user-manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplymichael%2Fexpress-user-manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simplymichael%2Fexpress-user-manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simplymichael","download_url":"https://codeload.github.com/simplymichael/express-user-manager/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248590070,"owners_count":21129743,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","authorization","express","expressjs","login","login-system","node","nodejs","registration","user","user-management","user-management-system"],"created_at":"2024-09-30T18:08:56.311Z","updated_at":"2025-04-13T19:51:53.883Z","avatar_url":"https://github.com/simplymichael.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Express User Manager\n[![npm](https://img.shields.io/npm/v/express-user-manager)](https://npmjs.com/package/express-user-manager)\n[![Travis build](https://img.shields.io/travis/com/simplymichael/express-user-manager)](https://travis-ci.com/github/simplymichael/express-user-manager)\n[![Codecov](https://img.shields.io/codecov/c/github/simplymichael/express-user-manager)](https://codecov.io/gh/simplymichael/express-user-manager)\n[![npm downloads](https://img.shields.io/npm/dm/express-user-manager)](https://npm.im/express-user-manager)\n[![GitHub License](https://img.shields.io/github/license/simplymichael/express-user-manager)](https://github.com/simplymichael/express-user-manager/LICENSE.md)\n[![Conventional commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-brightgreen.svg)](https://conventionalcommits.org)\n\nA user management and authentication library for Express apps.\n\nIt automatically creates and adds the following API endpoints to an Express app:\n\n- user registration\n- user login\n- user logout\n- user retrieval\n- users listing\n- user searching\n- user data update\n- user account deletion\n\nAdditional features include:\n\n- customizable API endpoints\n- support for multiple database engines and data-storage mechanisms\n- customization of the minimum and maximum length of passwords\n- specification of non-secure passwords that should not be allowed for use as passwords\n\n**New in V3.0.0**: Support for [Hooks](#hooks)\n\n# Table of Contents\n\n- **[Installation](#installation)**\n- **[Quick start](#quick-start)**\n- **[The `init` method](#the-init-method)**\n- **[Configuration](#configuration)**\n    - **[Environment variables](#environment-variables)**\n    - **[The `config` method](#the-config-method)**\n    - **[Specifying custom API endpoints](#specifying-custom-api-endpoints)**\n- **[Built-in middlewares](#built-in-middlewares)**\n- **[Hooks](#hooks)**\n    - **[Available hooks](#available-hooks)**\n        - **[Request hooks](#request-hooks)**\n        - **[Response hooks](#response-hooks)**\n    - **[Registering request and response hooks](#registering-request-and-response-hooks)**\n        - **[Registering hooks: examples](#registering-hooks-examples)**\n    - **[Unregistering request and response hooks](#unregistering-request-and-response-hooks)**\n        - **[Unregistering hooks: examples](#unregistering-hooks-examples)**\n- **[Built-in data stores (database drivers)](#built-in-data-stores)**\n- **[Emitted events](#emitted-events)**\n    - **[Events emitted by the database](#events-emitted-by-the-database)**\n    - **[Events emitted by request handlers](#events-emitted-by-request-handlers)**\n    - **[Events emitted by middlewares](#events-emitted-by-middlewares)**\n- **[Password constraints](#password-constraints)**\n- **[Usage as a stand-alone server](#usage-as-a-stand-alone-server)**\n- **[Requests and responses](#requests-and-responses)**\n- **[Contributing](#contributing)**\n    - **[Report a bug](#report-a-bug)**\n    - **[Request a new feature](#request-a-new-feature)**\n    - **[Submit a pull request](#submit-a-pull-request)**\n    - **Checkout the [Contributing guide](#contributing-guide)**\n- **[CHANGELOG](#changelog)**\n- **[License](#license)**\n- **[Author](#author)**\n\n## Installation\n`npm install --save express-user-manager`\n\n## Quick start\n```javascript\nconst express = require('express');\nconst userManager = require('express-user-manager');\nconst app = express();\n\n/**\n * Setup the datastore using any of the currently supported database adapters:\n *   - mongoose: for MongoDB\n *   - sequelize: for any of the other supported database engines:\n *     MySQL | MariaDB | SQLite | Microsoft SQL Server | Postgres | In-memory DB\n *     (See the section on \"Built-in data stores\" for supported database engines)\n */\nconst dbAdapter = 'mongoose'; // OR 'sequelize'\nconst store = userManager.getDbAdapter(dbAdapter);\n\n// Bind the routes under [apiMountPoint] (default: ***/api/users***):\nuserManager.listen(expressApp, apiMountPoint = '/api/users', customRoutes = {});\n\n(async function() {\n  const server = http.createServer(app);\n\n  // Establish a connection to the data store\n  // Ensure the db is connected before binding the server to the port\n  await store.connect({\n    host: DB_HOST, // optional, default: 'localhost'\n    port: DB_PORT, // optional\n    user: DB_USERNAME, // optional\n    pass: DB_PASSWORD, // optional\n    engine: DB_ENGINE, // optional if the adapter is \"mongoose\" or if the value is \"memory\" and the adapter is \"sequelize\"; required otherwise\n    dbName: DB_DBNAME, // optional, default: 'users'\n    storagePath: DB_STORAGE_PATH, // optional, required if \"engine\" is set to \"sqlite\"\n    debug: DB_DEBUG, // optional, default: false\n    exitOnFail: EXIT_ON_DB_CONNECT_FAIL // optional, default: true\n  });\n\n  // Proceed with normal server initialization tasks\n  server.listen(PORT);\n  server.on('error', onError);\n  server.on('listening', onListening);\n })();\n\n// Optionally listen for and handle events\n// (See the Emitted events section for more)\nuserManager.on(EVENT_NAME, function(data) {\n  // do something with data\n});\n```\n\n**Quick notes**\n- The `expressApp` parameter has the following constraints:\n    - It must be an express app (that is, an app created using `const app = express()`)\n    - It MUST NOT be an express server, that is, it must not have been passed to `http.createServer(app)`\n- The `apiMountPoint` parameter allows you to specify the base API route.\n  Every request to the API will be relative to this base route. The default is `/api/users`.\n- The `customRoutes` parameter is an object that allows customization of the routes.\n\n  (See **[Specifying custom API endpoints](#specifying-custom-api-endpoints)** for more)\n- If your ***expressApp*** has its own custom routing in place,\n  make sure to call `userManager.listen(expressApp)` before setting up your app's custom 404 handler.\n\n  This is because your app's 404 handler is meant to trap requests sent to routes that have no explicit handler in your app's routing system.\n\n  Consequently, if you setup your app's custom 404 handler before calling `userManager.listen()`,\n  requests to routes handled by the `userManager`'s routing system will never get to it as they will be trapped and handled by your 404 handler.\n\n## The `init` method\nThe `init` method provides a shortcut way to perform the setup and initialization steps above.\n\nIt is an `async` function that runs setup and initialization tasks, connects to the database, then starts listening for requests,\nall in a single step: `await init(app, options);`.\n\nIt takes two parameters:\n- an express.js app as the first argument\n- an object\n  (with the same signature as the object passed to the **[`config`](#the-config-method)** method)\n  as the second argument.\n\n## Configuration\nexpress-user-manager can be configured in several ways:\n\n- using environment variables (See the **[Environment variables](#environment-variables)** section)\n- using the `config` method (See **[The config method](#the-config-method)**)\n- passing configuration options as the second parameter to the `init(app, options)` method\n- using a combination of environment variables and the `config` method\n    - if configuration is not set via `config`, then configuration values are searched in environment variables.\n    - if only some configuration options are set using `config`, then the others are searched for in environment variables.\n    - The configuration options set via `config` take precedence over environment variables.\n    - the arguments passed to the `listen` or `init` methods take precedence over configuration options set using `config`.\n\n### Environment variables\n\n- **`NODE_ENV`** (*string*): The environment in which the app is running: *development*, *production*, *staging*, *test*, etc.\n- **`API_MOUNT_POINT`** (*string*): The route under which to listen for API requests, default is: `/api/users`\n- **`PORT`**: The port on which the server is running (or should run, if using as a stand-alone server)\n- **`DB_ENGINE`**: The database engine to use. Should be one of the supported databases.\n  (See **[Built-in data stores](#built-in-data-stores)**)\n- **`DB_ADAPTER`**: The adapter to use. Set it to `mongoose` if using MongoDB; Set it to `sequelize` otherwise.\n- **`DB_STORAGE_PATH`**: Define this only when the **DB_ENGINE** is set to `sqlite`.\n- **`DB_HOST`**: The database host\n- **`DB_USERNAME`**: The database user\n- **`DB_PASSWORD`**: The database user's password\n- **`DB_DBNAME`**: The name of the database\n- **`DB_PORT`**: The port on which the database is running\n- **`DB_DEBUG`**: Set to `true` or a non-zero integer to display debug output for the database.\n- **`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.\n- **`SESSION_SECRET`** (*string*)\n- **`AUTH_TOKEN_SECRET`** (*string*)\n- **`AUTH_TOKEN_EXPIRY`** (*number*): Authorization token expiry (in seconds)\n- **`PASSWORD_MIN_LENGTH`** (*number*)\n- **`PASSWORD_MAX_LENGTH`** (*number*)\n- **`DISALLOWED_PASSWORDS`**: (*array*): A comma-separated list of weak/non-secure passwords that should not allowed to be used as passwords\n\n**Note**: **express-user-manager** uses the [dotenv][dotenv] package,\nso a quick and easy way to define the above variables is to create a ***.env*** file\nat the root of your project directory, and add them to the file\nand they will automatically be picked up. [Sample .env file][env]\n\n### The `config` method\nAs stated earlier in the [Configuration](#configuration) section,\none of the ways you can configure *express-user-manager* is by using the `config` method.\n\nThis method provides an alternate way to pass configuration values to *express-user-manager**\nif you haven't done (or are unable to do) so via environment variables.\n\nBelow is an example touching on every setting:\n- **`apiMountPoint`**: (*string*) specifies the users API routes base route\n- **`password`**: (*object*) for configuring minimum and maximum password length, as well as disallowed passwords\n- **`routes`**: (*object*) for setting up [custom API endpoints](#specifying-custom-api-endpoints)\n- **`db`**: (*object*) encapsulating database connection information\n- **`security`**: (*object*) for configuring session and authorization tokens and expiry\n\n```javascript\nconst express = require('express');\nconst userManager = require('express-user-manager');\nconst app = express();\nconst dbAdapter = 'mongoose'; // OR 'sequelize'\n\n// Call config(options) to configure the app\nuserManager.config({\n  apiMountPoint: {string}, // The base route under which to listen for API requests\n\n  password: { // {object} for password configuration\n    minLength: {number}, // minimum length of user passwords, default: 6,\n    maxLength: {number}, // maximum length of user passwords, default: 20\n    disallowed: {string | array}, // comma-separated string or array of strings considered weak/non-secure passwords\n  },\n\n  routes: { // {object} for configuring custom routes, with members\n    list: {string}, // specifies the path for getting users listing\n    search: {string}, // specifies the path for searching users\n    getUser: {string}, // specifies the path for getting a user's details via their username, a /:{username} is appended to this path\n    signup: {string}, // specifies the user registration path\n    login: {string}, // specifies user authentication path,\n    logout: {string}, // defines the logout path\n    updateUser: {string}, // specifies the path for updating a user's data\n    deleteUser: {string} // specifies the path for deleting a user, a /:{userId} is appended to this path\n  },\n\n  db: { // {object} for configuring the database connection\n    adapter: {string}, // the adapter to use. valid values include 'mongoose', 'sequelize'\n    host: {mixed}, // database host\n    port: {number}, // database port\n    user: {string}, // database user\n    pass: {string}, // database user's password\n    engine: {string}, // the database engine, when the adapter is set to \"sequelize\". values: 'memory', 'mariadb', 'mssql', 'mysql', 'postgres', 'sqlite'\n    dbName: {string}, // name of the database to connect to\n    storagePath: {string}, // the database storage path, only valid when \"engine\" is \"sqlite\". combined with `dbName`: `${storagePath}/${dbName}.sqlite`\n    debug: {boolean}, // a value of true outputs database debug info\n    exitOnFail: {boolean}, // set to true to kill the Node process if database connection fails\n  },\n\n  security: { // {object} for configuring security\n    sessionSecret: {string}, // a key for encrypting the session\n    authTokenSecret: {string}, // a key for signing the authorization token\n    authTokenExpiry: {number}, // the expiry time of the authorization token (in seconds), example: 60 * 60 * 24\n  }\n});\n\nasync(() =\u003e {\n  /**\n   * The dbAdapter argument is not required if it is either:  \n   *   - specified in the db section of the call to config or as\n   *   - set using the DB_ADAPTER environment variable\n   */\n  const store = userManager.getDbAdapter([dbAdapter]);\n\n  /**\n   * The connectionOptions are not required if the values:\n   *   - are already specified in the db section of the call to config\n   *   - are set using the DB_* environment variables\n   */\n  await store.connect([connectionOptions]);\n\n  // If the dbAdapter and connection values are already specified\n  // via config or via environment variables,\n  // then the above two calls can be tersely combined in a single call:\n  // await userManager.getDbAdapter().connect();\n});\n\n// Bind request listeners\nuserManager.listen(expressApp);\n```\n\n**Notes on configuration settings**:\n- Any of the above settings can be omitted in the call to `config` if they are already defined using environment variables.\n  The exception to this is **`routes`**, which cannot be set via an environment variable.\n  However, you can set it using the third parameter to the call to `listen(app, apiMountPoint, routes)`.\n- If a setting is defined as an environment variable and also set in the call to `config`,\n  the value set in `config` will take precedence and be used instead.\n- The **`apiMountPoint`** can be set (in increasing order of precedence):\n    - using the environment variable **API_MOUNT_POINT**\n    - using the **`apiMountPoint`** key in the options to `config`\n    - as the second parameter to the call to `listen(app, apiMountPoint, routes)`\n\n### Specifying custom API endpoints\nTo customize the request paths, either:\n- pass a `routes` property with the API endpoints to `config`:\n  ```javascript\n  userManager.config({\n    routes: customApiEndpoints\n  });\n\n  userManager.listen(expressApp, apiMountPoint);\n  ```\n- pass an object with the API endpoints as the last parameter to `userManager.listen`:\n  `userManager.listen(expressApp, apiMountPoint, customApiEndpoints)`\n\nBelow is the default definition of the API Endpoints, which can be modified for your custom routes:\n```javascript\nconst customApiEndpoints = {\n  list       : '/',       // Resolves to [apiMountPoint]/\n  search     : '/search', // Resolves to [apiMountPoint]/search\n  getUser    : '/user',   // Resolves to [apiMountPoint]/user/:username\n  signup     : '/',       // Resolves to [apiMountPoint]/\n  login      : '/login',  // Resolves to [apiMountPoint]/login\n  logout     : '/logout', // Resolves to [apiMountPoint]/logout\n  updateUser : '/',       // Resolves to [apiMountPoint]/\n  deleteUser : '/user',   // Resolves to [apiMountPoint]/user/:userId\n};\n```\n\n#### API endpoints object properties\nAs seen above, the default object has a number of properties, each corresponding to a request path:\n- **list** : Specifies the path to get users listing\n- **search** : Specifies the path to search for users\n- **getUser** : Specifies the path to get a user by username\n(a `/:username` is automatically appended to the end of this route)\n- **signup** : Specifies the path for creating (i.e., registering) a new user\n- **login** : Specifies the path for logging in a user (an authorization key is returned on successful login)\n- **logout** : Specifies the path to log out a user\n- **updateUser**: Specifies the path for updating user information\n- **deleteUser** : Specifies the path for deleting user by id\n(a `/:userId` is automatically appended to the end of this route)\n\n## Built-in middlewares\nThe **userManager** module provides some middlewares.\nYou can get them by calling: `userManager.get('middlewares');`.\nThis will return an object with the following middlewares:\n- **authorized**:\n  For protected resources.\n  It ensures an access/authorization token is sent along with the request\n  using the ***Authorization*** header.\n- **loadUser**:\n  Loads the current user (identified by *username*) into the request,\n  to that it is available to every other middleware in the middleware chain.\n  The username is sent as part of the request parameters (*request.params*)\n- **loggedIn**:\n  Ensures a user is logged in before they can perform the requested action.\n- **notLoggedIn**\n  Ensures that the target action is available only to users who are not logged in.\n  For example, registration and login should (normally) not be permissible\n  if the current user is already logged in.\n- **restrictUserToSelf**:\n  Constrains a user to performing certain actions only on their own account.\n\n## Hooks\nHooks are a mechanism to allow you hook into different parts of the application's lifecycle.\n\n### Available hooks\n- \u003ca name=\"request-hooks\"\u003e**Request hooks**\u003c/a\u003e: allow you define and register custom request middlewares.\n  They give you the ability to modify the request any way you see fit.\n\n  Request hooks are called first before any other middleware in the middleware chain is called.\n  This gives you great flexibility in modifying the request before handing it over to other middleware functions in the chain.\n- \u003ca name=\"response-hooks\"\u003e**Response hooks**\u003c/a\u003e: let you define and register custom response-modifying functions.\n\n  Response hooks are called just before the response is sent back to the client,\n  giving you the ability to modify the response before it gets to the client.\n\n  **Note**: Currently, response hooks are only fired when the HTTP response status code is 200.\n\nYou can register a request or response hook for a single route, for multiple routes, or for all routes.\n\n#### Registering request and response hooks\nTo register a request or response hook:\n- define a middleware, which is just a fancy word for a function that takes three parameters: `req`, `res`, `next`.\n\n  If you intend to hand over processing to the next handler in the chain (recommended),\n  remember to call `next()` from within your middleware when you are done; Or call `next(error)` to pass execution to the error handler.\n- decide on which route (or routes) the middleware should be registered for: single-route, multi-route, global.\n  The route(s) should correspond to a key or keys in the [API endpoints configuration object](#api-endpoints-object-properties).\n\n  Multiple hooks can be registered on a given route.\n- Register the hook:\n    - call `userManager.addRequestHook(target, middlewareFn)` to register a request hook\n    - call `userManager.addResponseHook(target, middlewareFn)` to register a response hook\n\n  The first parameter to `addRequestHook` or `addResponseHook` is a string or an array of strings that specify the target of the hook.\n  Possible values include:\n    - `*`: represents a global hook, which will register the hook for every path\n    - pathName, e.g `login`: represents a single-route hook, which will register the hook for the specified route\n    - list of pathNames, e.g `['login', 'signup', ...]`: represents multi-route hooks, which will register the hook for every path in the array\n\n#### Registering hooks: examples\n- Register a request hook for every route:\n  ```javascript\n  userManager.addRequestHook('*', function(req, res, next) {\n    // Do something interesting here, with the request or the response.\n    req.accessTime = Date.now();\n\n    // Call next to pass control onto the next middleware function\n    next();\n  });\n  ```\n- Register a response hook for the signup route:\n  ```javascript\n  userManager.addResponseHook('signup', function(req, res, next) {\n    // You can for example set/append custom response headers:\n    res.append('Access-Control-Allow-Headers', '\u003cCUSTOM_HEADER\u003e');\n  });\n  ```\n- Register a response hook for multiple (*login* and *signup*) routes:\n  ```javascript\n  userManager.addResponseHook(['login', 'signup'], function(req, res, next) {\n    // Do something tangible\n    res.body.data.authenticated = true;\n  });\n  ```\n- Register a list of request hooks along with their corresponding function identifiers:\n  ```javascript\n  userManager.addRequestHook(['login', 'signup'], ['loginFnId', 'signupFnId'])\n  ```\n- Register multiple response hooks for a single (*signup*) route:\n  ```javascript\n  userManager.addResponseHook('signup', [callback1, callback2, ...]);\n  ```\n\n#### Unregistering request and response hooks\nYou also have the ability to unregister a hook when the hook is no longer needed.\nAnd you can unregister hooks globally, for only some routes, or for a single route.\n\n- To unregister a request hook, call `userManager.removeRequestHook(route [, callback])`.\n\n  If the optional `callback` parameter is not specified, every hook registered to that route will be removed.\n  Otherwise, only the particular callback function specified for the hook is removed.\n- To unregister a response hook, call `userManager.removeResponseHook(route, [callback])`;\n\n  If the optional `callback` parameter is not specified, every hook registered to that route will be removed.\n  Otherwise, only the particular callback function specified for the hook is removed.\n\n#### Unregistering hooks: examples\n- Unregister every request hook:\n  ```javascript\n  userManager.removeRequestHook('*');\n  ```\n- Unregister every response hook for the user signup route:\n  ```javascript\n  userManager.removeResponseHook('signup');\n  ```\n- Unregister only a request callback hook for the signup route:\n  ```javascript\n  UserManager.removeRequestHook('signup', fn);\n  ```\n\n  `fn` should be a named function registered with `userManager.addRequestHook('signup', fn)`;\n\n  Anonymous functions cannot be unregistered this way.\n- Unregister a list of request hooks along with their corresponding function identifiers:\n  ```javascript\n  userManager.removeRequestHook(['login', 'signup'], ['loginFnId', 'signupFnId']);\n  ```\n- Unregister every response hook for the passed routes list:\n  ```javascript\n  userManager.removeRequestHook(['login', 'signup']);\n  ```\n- Unregister multiple request hooks for a single (*login*) route:\n  ```javascript\n  userManager.removeRequestHook('login', [fn1, fn2, ...]);\n  ```\n\n\u003ca name=\"built-in-data-stores\"\u003e\u003c/a\u003e\n## Built-in data stores (database adapters and engines)\n- In-memory (Adapter: [sequelize](https://www.npmjs.com/package/sequelize))\n    - **Note**: In-memory storage should be used solely for quick prototyping and testing purposes. It is not recommended for use in production.\n- MariaDB (Adapter: [sequelize](https://www.npmjs.com/package/sequelize), Engine: `mariadb`)\n- Microsoft SQL Server (Adapter: [sequelize](https://www.npmjs.com/package/sequelize), Engine: `mssql`)\n- MongoDB (Adapter: [mongoose](https://www.npmjs.com/package/mongoose))\n- MysQL (Adapter: [sequelize](https://www.npmjs.com/package/sequelize), Engine: `mysql`)\n- Postgres (Adapter: [sequelize](https://www.npmjs.com/package/sequelize), Engine: `postgres`)\n- SQLite (Adapter: [sequelize](https://www.npmjs.com/package/sequelize), Engine: `sqlite`)\n\n\u003ca name=\"emitted-events\"\u003e\u003c/a\u003e\n## Emitted events\n\n\u003ca name=\"events-emitted-by-the-database\"\u003e\u003c/a\u003e\n### Events emitted by the database\n- dbConnection\n- dbDisconnect\n- createUser\n\n\u003ca name=\"events-emitted-by-request-handlers\"\u003e\u003c/a\u003e\n### Events emitted by request handlers\n- signupError\n- signupSuccess\n- loginError\n- loginSuccess\n- logoutSuccess\n- getUsersError\n- getUsersSuccess\n- searchUsersError\n- searchUsersSuccess\n- getUserSuccess\n- updateUserError\n- updateUserSuccess\n- deleteUserError\n- deleteUserSuccess\n\n\u003ca name=\"events-emitted-by-middlewares\"\u003e\u003c/a\u003e\n### Events emitted by middlewares\n- actionNotPermittedError\n- authorizationError\n- authenticationError\n\n\u003ca name=\"password-constraints\"\u003e\u003c/a\u003e\n## Password constraints\n- minimum length of `PASSWORD_MIN_LENGTH` environment variable\n- maximum length of `PASSWORD_MAX_LENGTH` environment variable\n- must contain at least one number\n- must contain at least an uppercase character\n- must contain at least a lowercase character\n- must not be among the values specified in the `DISALLOWED_PASSWORDS` environment variable\n\n## Usage as a stand-alone server\nThe package comes with a built-in express server that allows you run it as a stand-alone server.\n\nTo run it as a stand-alone server, do the following:  \n- Ensure you have a server running for your preferred database engine.\n  (See **[Setting up test databases](#setting-up-test-databases)** for some examples)\n- Define the environment variables listed in the **[Environment variables](#environment-variables)** section.\n- start the server, using one of these two methods:\n    - Run `node express-user-manager/src/server` from within the parent directory containing the express-user-manager package.\n    - `require('express-user-manager/src/server')();` from within a `node.js` script. For example, inside an `index.js` file.\n      Then run the file using node: `node index.js`.\n\n**Note**: The built-in server runs using the default route/path settings. That means:\n- it runs under the `/api/users` base route (mount point).\n- it uses the default request paths. (See the section on **[Requests and responses](#requests-and-responses)**)\n\n## Requests and responses\nEvery route below is assumed to begin (i.e., prefixed) with the base API route (or mount point).\nThe default base API route is **`/api/users`**.\n\n- **Create user**\n    - route: `POST /`\n    - protected: `false`\n    - request headers: none\n    - request parameters: none\n    - request body: `{ firstname, lastname, username, email, password, confirmPassword }`\n    - response:\n      ```javascript\n      {\n        \"data\": {\n          \"user\": { id, firstname, lastname, fullname, email, username, signupDate }\n        }\n      }\n      ```\n- **Get user details by username**\n    - route: `GET /user/USERNAME`\n    - protected: false\n    - request headers: none\n    - request parameters: none\n    - request body: none\n    - response:\n      ```javascript\n      {\n        \"data\": {\n          \"user\": { id, firstname, lastname, fullname, email, username, signupDate }\n        }\n      }\n      ```\n- **Retrieve list of users**\n    - route: `GET /`\n    - protected: `false`\n    - request headers: none\n    - request parameters:\n        - `firstname` (string, optional): get users matching {firstname}\n        - `lastname` (string, optional): get users matching {lastname}\n        - `sort` (string, optional)\n        - `page` (number, optional, default = 1)\n        - `limit` (number, optional, default = 20)\n    - request body: none\n    - response:\n      ```javascript\n      {\n        \"data\": {\n          \"total\": TOTAL_COUNT_OF_MATCHING_RESULTS,\n          \"length\": COUNT_OF_CURRENT_RESULTS_RETURNED, // determined by \"page\" and \"limit\"\n          \"users\": [\n            { id, firstname, lastname, fullname, email, username, signupDate },\n            { id, firstname, lastname, fullname, email, username, signupDate },\n            ...\n          ]\n        }\n      }\n      ```\n- **Search for users**\n    - route: `GET /search?query=SEARCH_TERM`\n    - protected: `false`\n    - request headers: none\n    - request parameters:\n        - `query` (string, required)\n        - `sort` (string, optional)\n        - `by` (string, optional)\n        - `page` (number, optional, default = 1)\n        - `limit` (number, optional, default = 20)\n    - request body: none\n    - response:\n      ```javascript\n      {\n        \"data\": {\n          \"total\": TOTAL_COUNT_OF_MATCHING_RESULTS,\n          \"length\": COUNT_OF_CURRENT_RESULTS_RETURNED, // determined by \"page\" and \"limit\"\n          \"users\": [\n            { id, firstname, lastname, fullname, email, username, signupDate },\n            { id, firstname, lastname, fullname, email, username, signupDate },\n            ...\n          ]\n        }\n      }\n      ```\n    - examples:  \n        - Search for users with **james** in their firstname, lastname, username, or email:\n\n          `GET HOST:PORT/api/users/search?query=james`\n        - Search for users with **james** in their username or email:\n\n          `GET HOST:PORT/api/users/search?query=james\u0026by=username:email`\n        - Sort by firstname (asc), lastname (asc), email (desc), creationDate (asc):\n\n          `GET HOST:PORT/api/users/search?query=james\u0026sort=firstname:asc=lastname=email:desc=creationDate`\n        - Return the 3rd page of results and limit returned results to a maximum of 15 users:\n\n          `GET HOST:PORT/api/users/search?query=james\u0026page=3\u0026limit=15`\n- **Login**\n    - route: `POST /login`\n    - protected: `false`\n    - request headers: none\n    - request parameters: none\n    - request body:\n      ```javascript\n      {\n        \"login\": EMAIL | USERNAME,\n        \"password\": USER_PASSWORD,\n      }\n      ```\n    - response:\n      ```\n      {\n        \"data\": {\n          \"user\": { id, firstname, lastname, fullname, email, username, signupDate },\n          \"authorization\": {\n            \"token\": \"Bearer TOKEN_STRING\",\n            \"expiresIn\": \"86400s\"\n          }\n        }\n      }\n      ```\n- **Logout**\n    - route: `GET /logout`\n    - protected: `false`\n    - request headers: none\n    - request body: none\n    - request parameters: none\n    - response: `{}`\n- **Update user data**\n    - route: `PUT /`\n    - protected: `true`\n    - request headers:\n      ```javascript\n      {\n        \"Authorization\": \"Bearer TOKEN_STRING\"\n      }\n      ```\n    - request parameters: none\n    - request body: `{ id, firstname, lastname, username, email }`\n    - response:\n      ```javascript\n      {\n        \"data\": {\n          \"user\": { id, firstname, lastname, fullname, email, username, signupDate }\n        }\n      }\n      ```\n- **Delete user by ID**\n    - route: `DELETE /user/USER_ID`\n    - protected: `true`\n    - request headers:\n      ```javascript\n      {\n        \"Authorization\": \"Bearer TOKEN_STRING\"\n      }\n      ```\n    - request body:\n      ```javascript\n      {\n        \"userId\": USER_ID\n      }\n      ```\n    - response `{}`\n\n## Contributing\n- \u003ca name=\"report-a-bug\"\u003e[Report a bug][bug]\u003c/a\u003e\n- \u003ca name=\"request-a-new-feature\"\u003e[Request a new feature][fr]\u003c/a\u003e\n- \u003ca name=\"submit-a-pull-request\"\u003e[Submit a pull request][pr]\u003c/a\u003e\n- \u003ca name=\"contributing-guide\"\u003eCheckout the [Contributing guide][contribute]\u003c/a\u003e\n\n## CHANGELOG\nSee [CHANGELOG][changelog]\n\n## License\n[MIT License][license]\n\n## Author\n[Simplymichael](https://github.com/simplymichael) ([simplymichaelorji@gmail.com](mailto:simplymichaelorji@gmail.com))\n\n\n[pr]: https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request\n[fr]: https://github.com/simplymichael/express-user-manager/labels/feature%20request\n[bug]: https://github.com/simplymichael/express-user-manager/labels/bug\n[env]: https://github.com/simplymichael/express-user-manager/blob/master/.env.example\n[dotenv]: https://www.npmjs.com/package/dotenv\n[license]: https://github.com/simplymichael/express-user-manager/blob/master/LICENSE.md\n[changelog]: https://github.com/simplymichael/express-user-manager/blob/master/CHANGELOG.md\n[contribute]: https://github.com/simplymichael/express-user-manager/blob/master/CONTRIBUTING.md\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimplymichael%2Fexpress-user-manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimplymichael%2Fexpress-user-manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimplymichael%2Fexpress-user-manager/lists"}