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

api-accelerate

v1.2.1

Published

A framework for rapid development of reliable, functional APIs

Downloads

16

Readme

API Accelerate

A Node.js framework for building web APIs

API Accelerate is a Node.js framework that simplifies the development of APIs. It creates a wrapper around Express.js and extends it with services such as logging, database access and error handling.

Quick Start

Create and Run an App

1․ Create a new Node.js project.

mkdir test
cd test
npm init

2․ Install API Accelerate as a dependency:

npm install api-accelerate --save

3․ Add this start script line to package.js:

  "scripts": {
    ...
    "start": "./node_modules/api-accelerate/bin/run.js"
  },

4․ Create these files in the project:

./config.js

module.exports = {
  server:{
    port: 10000
  }
}

./routes/test.js

module.exports = (router) => {

var test = require('../controllers/test')

router.route('/')
  .get(test.get);
}

./controllers/test.js

exports.get = async (req, res) => {
  req.context.responder.text(req, res, "Hello, world\n");
}

5․ Run the server in a terminal window:

npm start

6․ In another window, issue a request using a client such as Curl:

curl http://127.0.0.1:10000

You should see the response Hello, world.

7․ Terminate the app by pressing Ctl-C in the app window.

Explanation of the Code

The start script instantiates, configures and runs an app.

The framework looks for the configuration file in the project root. In this example, the configuration is minimal, specifying only the port number on which the server listens.

The framework then looks in folder routes for route files. It loads each file it finds and calls the entry point to initialize it. Note that the framework passes a router object to which routes are added.

This particular route file sets up a route to GET on path /. The route is bound to controller method test. When the controller receives the request, it extracts a responder object and uses it to respond back to the client with a text message.

This app can now be extended by adding more routes and controllers. The configuration can be extended to use additional capabilities, such as database access.

Demo Apps

A set of demo apps can be found at API Accelerate Demos.

These demonstrate various features of the framework.

Application Structure

Model-View-Controller

API Accelerate apps follow a model–view–controller pattern.

Views are API endpoints, associated with a route. Models manage business logic, including storage. Controllers tie views and models together, providing coordination and translation.

The interactions between models, views and controllers are consistent with the MVC pattern. Route files (views) invoke controllers, and controllers interact with models and pass information back to views.

File Structure

By convention, a project includes these folders:

├── controllers     # Controller files
├── models          # Model files
└── routes          # Routes files

Startup and Config

The API Accelerate run script starts the app. This script also provides control to stop the app gracefully. It listens for CTRL-C, terminates the app and waits for it to finish.

Once the start script is configured in package.json, the app can be started with this command:

npm start

This command prompts API Accelerate to create, configure and run an app. It then looks for the config file in the project root and begin. By default, it scans folder routes for route files. It loads each file it finds and calls the entry point to configure it. Note that the framework passes an Express router object. Routes are added to this object per Express.

Requests

When a request comes in, the route passes it to a controller for processing. The controller receives the request, extracts a responder object, and uses it to respond back to the client.

The request object contains a property called app. This provides access to framework services, such as logging and database querying.

Configuration

Overview

The application requires a configuration file. A simple application can operate with minimal changes to the default configuration,

By default, the framework will look for file config.js file in the root directory.

Configuration File Example

This is an example of a minimal configuration file:

module.exports = {
  server:{
    port:               10000
  }
}

The only configuration setting is the port number on which the server listens. Even this setting is optional - the port number defaults to 80.

Configuration Sections

The config file is organized in sections.

| Section | Description | | --- | --- | | server | Defines the general operation of the server. | | router | Changes the default router options and adds additional routers if needed. | | logger | Sets logging options to control which log entry types are emitted. | | database | Defines database access parameters, such as server address and credentials. | | app | Provides settings the app logic can use to configure its operations. |

Server Configuration Section

| Parameter | Description | | --- | --- | | port | The port the server listens on. Default is 80. | | timeout | The interval in milliseconds after which a request will time out. Default is 30 seconds. | | prettify_json | Specifies whether JSON strings sent back to the client are formatted for human readability. The default is false. |

Routing

API Accelerate scans the routes folder recursively to find and invoke all route files. It passes an Express Router object to the entry point.

The application can provide as many or as few route files as needed. The general guidance is that each route file should define a set of related routes for a particular process or resource type.

A route file should not include any logic other than what is needed to route requests to a controller.

Example route file:

// persons.js

module.exports = (router) => {

var persons = require('../controllers/persons')

router.route('/persons')
  .get(persons.list)
  .post(persons.create);

router.route('/persons/:person_id')
  .get(persons.get)
  .put(persons.update);
}

Router Configuration

It is possible to omit the router configuration section, in which case the default behavior will be implemented. This is sufficient for many applications. Those that need more control can use the following parameters:

| Parameter | Description | | --- | --- | | name | An optional identifying name. This can be useful in logs. | | path | The base path for all routes. Default is "/". | | folder | The folder where route files are located. Default is "/routes". | | params | Additional parameters specific to the router which are used by application logic. |

It is possible to define more than one router by providing an array of router config objects. (By convention, the parameter name is called "routers" when there is more than one. However, the framework treats keywords "router" and "routers" identically.

Controllers

A controller receives requests and invokes the appropriate logic. A standard Express controller method receives Express request and response objects as parameters.

The framework exposes a context object to the app. It injects this object into the Express request object so that it is available to app logic. The context object provides services such as a responder, databaser management, logging, and others.

The context can be obtained in this way:

  var context = req.context;

Here is an example of a simple controller.

const test = require('../models/test');

exports.get = async (req, res) => {
  try {
    var context = req.context;
    var data = await test.get(context);
    context.responder.json(req, res, data);
  } catch(e) {
    context.responder.error(req,res,e);
  }
}

Note that the logic extracts the context from the request and passes it to the model for its use. The controller sends a response back to the client via the responder object. This object provides a variety of response methods. In this example, it returns data explicitly in JSON format. The responder takes care of the details of formatting the data and returning the proper mime type and response code.

Note that the controller catches and handles errors. In many cases, the responder can automatically map an error to an appropriate HTTP response code. For example, if the model throws Record Not Found, the controller simply passes this to the responder, which generates a 404 response. This reduces the complexity of the controller code and ensures a consistent treatment of errors.

Responder Methods

| Method | Description | HTTP Code | Content Type | | --- | --- | --- | --- | | ok (req, res) | Returns a standard 200 response with no data. | 200 | | | text (req, res, s) | Returns a string value. | 200 | Express default | | html (req, res, s) | Returns a block of HTML code. | 200 | text/html | | respond (req, res, data) | Responds with data in a suitable format. It inspects the Accept headers provided by the client and formats the data and sets the Content-Type accordingly. | 200 | Default is application/json, unless Accept header dictates otherwise. | | json (req, res, data) | Responds with data in JSON format. | 200 | text/json | | csv (req, res, data) | Responds with data in CSV format. | 200 | text/csv | | tabular (req, res, data) | Responds with data in a tabular text form. | 200 | text/plain | | plain_text (req, res, s) | Responds with plain text. | 200 | text/plain | | notImplemented (req, res, msg) | Returns HTTP code Not Implemented. | 501 | N/A | | notAllowed (req, res, msg) | Returns HTTP code Bad Request. | 400 | N/A | | badRequest (req, res, msg) | Returns HTTP code Bad Request. | 400 | N/A | | notFound (req, res, msg) | Returns HTTP code Not Found. | 404 | N/A | | unauthorized (req, res, msg) | Returns HTTP code Unauthorized. | 401 | N/A | | failure (req, res, err) | Returns HTTP code Server Error. | 500 | N/A | | error (req, res, err) | Analyzes the exception object passed to it and selects an appropriate response code. | See descriptions in Errors section | N/A |

CSV and Tabular Responses

The csv and tabular responder methods will convert a record set to the corresponding output formats. A tabular output is simply text with cells evenly padded.

These two methods both convert an array of record objects to a grid format with headers. When the record array is passed in, the responder will parse the keys of the records to determine the column names. Example:

exports.get = async (req, res) => {
  try {
    var context = req.context;

    var records = [
      { first: 'George', last: 'Washington' },
      { first: 'John', last: 'Adams' },
      { first: 'Thomas', last: 'Jefferson' }
    ]; 

    return context.responder.tabular(req, res, records);

  } catch(e) {context.responder.error(req, res, e)}
}

Output:

+--------+------------+
| first  | last       |
+--------+------------+
| George | Washington |
| John   | Adams      |
| Thomas | Jefferson  |
+--------+------------+

There is an option to pass an explicit list of column names. Example:

exports.get = async (req, res) => {
  try {
    var context = req.context;

    var records = [
      { first: 'George', last: 'Washington' },
      { first: 'John', last: 'Adams' },
      { first: 'Thomas', last: 'Jefferson' }
    ]; 

    var columns = [
      { key: 'first', name: 'First Name' },
      { key: 'last', name: 'Last Name' }
    ];

    return context.responder.tabular(req, res, records, columns);

  } catch(e) {context.responder.error(req, res, e)}

}

Output:

+------------+------------+
| First Name | Last Name  |
+------------+------------+
| George     | Washington |
| John       | Adams      |
| Thomas     | Jefferson  |
+------------+------------+

Errors

The framework provides a set of error classes for throwing exceptions. These are exported in the accelerate module. Example:

const SystemError = require('api-accelerate').errors.SystemError;
throw new SystemError("<ERROR DESCRIPTION", <DATA OBJECT>);

All error classes are based on the Node.js error class (https://nodejs.org/api/errors.html#class-error)

If an error is passed to the response object, it will be mapped to an HTTP error code which is sent back to the client.

Error classes

| Class | Parameters | Description | HTTP Code | | --- | --- | --- | --- | | Error | message | A general error that produces a message | 500 | | SystemError | message, info | Thrown when there is some problem at the system level. | 500 | | NotFoundError | message, id, info | The identified resource is not found. | 404 | | DuplicateError | message, id, info | The identity of a resource already exists. | 400 | | NotAllowedError | message, info | The requested operation is not allowed. | 401 | | NotImplementedError | message | The request type is not implemented yet. | 501 | | FileError | message, info | An error occurred during file I/O. | 400 | | HttpError | message, url, info | An error occurred accessing the given URL. This can occur when the server makes an HTTP request to another service. | 400 | | DatabaseError | query, values, info | An error occurred when performing a database query, where the error is something other than "not found" or "duplicate". | 400 | | ImageError | message, info | An error occurred when processing an image or image file. | 400 |

Error Parameters

| Parameter | Type | Description | | --- | --- | --- | | message | String | The primary message associated with the error. | | info | Object | An object with additional information about the error. | | id | Scalar | The ID or key associated with the error. | | url | String | The URL associated with the error. | | query | String | The SQL query associated with the error. | | values | Array | The query values associated with the error. |

Models

Per the MVC pattern, models are designed to handle rules, data, and business logic.

Generally, model methods should accept the server object so that it has access to services such as logging and database access.

Models that perform I/O need to be implemented with asynchronous callbacks or promises so they do not block request threads.

Here is an example of a model that performs loan amortization:

// models/amortization.js

exports.amortize = async (context, params) => {

  return new Promise((resolve, reject) => {
    var loan = {
      rate: params.rate,
      principal: params.principal,
      term: params.term,
      monthly: null,
      schedule: []
    }

    const round = (n) => {return Math.round(n * 100 / 100);};

    loan.monthly = round((((loan.rate / 12) * loan.principal)) / (1-(Math.pow(1 + (loan.rate / 12),(-loan.term)))));
    var balance = loan.principal;
    var month = 1;

    while (balance > 0) {

      var payment = Math.min(loan.monthly, balance);
      var interest = round((balance*loan.rate / 12));
      if (interest <= 1) {interest = 0;}
      var principal = payment - interest;

      var record = {
        month: month,
        balance: balance,
        payment: payment,
        interest: interest,
        principal: principal
      }
      loan.schedule.push(record);
      balance -= principal;
      month++;
    }
    resolve(loan);
  });

}

Custom Initialization

In some instances, the app needs to perform some custom initialization at startup. A streamlined way of accomplishing this is with an automatic initialization script.

At startup, the framework looks for a file called initialize.js in the root folder of the application. If found, the framework loads and invokes it. If not found, no action is taken.

initialize.js exports a function call with two parameters: the app object, provides access to framework services, and the app configuration.

module.exports = async (app, config) => {
 ... intialization logic here
}

Database

Overview

The framework natively supports MySQL database access. Postgres and other databases will be added in future. ORM support is under evaluation.

Configuration

It is necessary to configure the system for database access. The configuration specifies information such as the server address and credentials.

When the application starts, the framework uses the configuration settings to create a database access object which is then available in the app context.

Example of database configuration:

  database: {
    adapter:            "mysql",
    host:               process.env.DB_HOST        || 'localhost',
    port:               process.env.DB_PORT        || null,
    user:               process.env.DB_USER        || 'main',
    password:           process.env.DB_PASSWORD    || 'password',
    database:           process.env.DB_DATABASE    || 'persons',
    connections:        process.env.DB_CONNECTIONS || 10,
    logging: {
      connections:      true,
      queries:          true
    }
  }

This configuration specifies the host, port, user ID, password, and the number of connections. In addition, it specifies additional logging settings, which can be useful during development and testing.

Note this example also demonstrates the use of environment variables for configuration.

Database Configuration

| Parameter | Setting | Default | | --- | --- | --- | | name | An optional name given to this database configuration. | Optional setting - no default. | | adapter | The type of adapter, such as mysql. | Required setting - no default. | | host | The name of the host where the database server runs. | Required setting - no default. | | port | The port number for the database socket. | 3306 for MySQL. | | user | The user name for accessing the database server. | Required setting - no default. | | password | The user password for accessing the database server. | Required setting - no default. | | database | The name of the database on the server. | Required setting - no default. | | connections | The number of connections in the connection pool. This can be useful for performance tuning. | 100 | | logging | Settings for controlling logging output from the database logic, as a nested object. | N/A |

Database Logging Configuration

These settings can be useful for debugging.

| Parameter | Setting | Default | | --- | --- | --- | | connections | Log when connections are allocated or released. | false | | queries | Log all queries performed. | false |

Database Calls

The database is accessible from within the application to make calls. App property db is an object that can be used to establish a database connection.

Example
const get = async (context, person_id) => {
  return new Promise(async (resolve, reject) => {
    try {
      await context.db.perform(async (connection) => {
        var q = 
          "SELECT \
             person_id, first_name, last_name, email_address \
           FROM persons \
           WHERE person_id = ?";
        var v = [person_id]
        var data = await connection.readOne(q, v)
        resolve(data);
      });
    } catch(e) {
      reject(e);
    }
  });
}

Note that the caller of this method passes in the context along any parameters needed to perform the query. context.db is the adapter object that exposes methods used for making queries.

This logic must be wrapped in a promise because it performs asynchronous I/O.

Connection Management

Connections must be obtained, used, then released. The db object provides these methods for managing connections:

| Call | Description | | --- | --- | | perform(callback) | Establishes a connection then invokes the callback function. The callback contains logic for interacting with the database. When the function terminates, the connection is released automatically. | | connect() | Establish a connection for accessing the database. This connection must be released when no longer needed. | | release(connection) | Release a connection. Note the connection object also has a release method which invokes this. |

In this example, the perform method takes care of allocating a database connection, executing the app logic, then releasing the database connection. This is a safe way to prevent unreleased connections.

It is also possible to manually allocate and release the connection. This is not generally recommended but can be useful in some cases.

    try {
        var connection = context.db.connect();
        // perform a query
        resolve(data);
      });
    } catch(e) {
      reject(e);
    } finally {
      connection.release();
    }

In this case, the best practice is to release the connection in the finally block to ensure connections are not left open.

It is also a good practice to limit the amount of time spent inside the callback. Any logic that doesn't require a connection should be moved outside of the callback wherever possible. There are a limited number of connections available in the pool, so optimizing this can result in better performance.

Database Access Methods

The connection exposes methods used for accessing the database. Database methods all receive a SQL query, which may have placeholders, and an array of values to replace the placeholders. If no placeholders are provided, the values may be omitted.

| Call | Description | | --- | --- | | readOne(q, v) | This method expects to read exactly one record. If no records are available, it throws a NotFoundError. If more than one record is available, it throws a generic DatabaseError. | | readList(q, v) | This method reads zero or more records. | | insert(q, v) | This method inserts a new record. | | update(q, v) | This method updates one or more records. | | upsert(q, v, v2) | This performs a MySQL style INSERT ... ON ... UPDATE query. | | delete(q, v) | This method deletes one or more records. | | query(q, v) | This method makes any type of query. |

References

https://www.npmjs.com/package/mysql

Logging

Overview

TBD

Logger Configuration

| Parameter | Description | | --- | --- | | system | Log system information. Default is false. | | request | Log API requests. Default is false. | | trace | Log trace calls. Default is false. | | query | Log database queries. Default is false. | | exception | Log exceptions. Default is true. |

Helpers

TBD

Handlers

The framework provides a set of predefined handlers that can be tailored to the application. A handler manages a process, such as the response to a particular type of request. For example, the framework provides a handler that manages the login process.

The process a handler implements is broken down into a series of steps specific to the handler. Each step is implemented with a default method, which can be overridden. Methods are overridden via matching setter methods exposed in the handler instance.

A handler instance is created based on the handler. This is typically done only once during app initialization, and then the instance is available for use during request processing.

Each handler has a unique name which is used to select the instance to create.

Handler Creation

The handlers object is made available in the app instance. This object provides function create, which is used to create a handler instance.

| Param | Description | | --- | --- | | name | The name of the handler. Mandatory. | | app | The instance of the app, which is passed from the framework to the initialization code. Mandatory. | | params | If provided, these params are used to configure the handler instance. If not provided, the handler may fetch configuration info from the app config object. Note that not all handlers require configuration info. Optional. |

The handler instance exposes setter functions which allow the app to override methods in the process. Each setter function returns the handler instance, so they can be chained together.

Example:

var handler = app.handlers.create('login_user', app)
  .authenticate((context, credentials) => {
  ... authenticate account using credentials
  }

More details and examples are provided in the sections below, including in Authentication.

Authentication

Overview

The framework provides basic configurable authentication using a Java Web Token (JWT). This will be extended in future with additional authentication methods and services.

The login process uses credentials to verify the user exists and to generate an access token and pass it back to the client. That token is used in the authentication process that takes place at the beginning of each subsequent request.

Other processes are needed to create and manage user accounts.

Authentication Handlers

The following handlers provide services related to account management and authentication:

| Handler | Description | | --- | --- | | login_user | Login based on credentials. | | authenticate_user | A middleware function that establishes a session for the user before processing a request. | | register_user | Register a new user account. | | change_user_password | Change the password for a user account. |

Future handlers will be provided for OAuth and other authentication needs.

Configuration

Authentication service handlers require some or all of these configuration parameters:

| Parameter | Setting | Default | | --- | --- | --- | | method | The type of authentication method to use. Currently only jwt is supported. | Required setting - no default. | | jwt_secret | Used for signing the token. | Required setting - no default. | | jwt_expiration | The maximum lifetime of the token, in seconds. | Defaults to 0, meaning never expires. | | password_hash_type | The type of hash used on passwords. | MD5 is the default and is currently the only supported algorithm. | | password_salt | A string that as appended to the password before hashing to create a unique hashing. | If not provided, the password is not hashed, which is generally a bad practice. | | regex_user_name | A regex string used to validate the user name. If not provided, no validation is performed. | None. | | regex_password | A regex string used to validate the user name. If not provided, no validation is performed. | None. |

By default, these parameters are provided in the configuration file in the auth section.

Login

The standard login is based on user name and password. The credentials are used to authenticate the account and generate an access token, which is passed back to the client.

The login handler process consists of these steps, executed in sequence:

| Action | Description | | --- | --- | | extract(context, req, res) | Extract the credentials from the request. | | encode(context, credentials) | Encode the password, by default with MD5. The password itself is never stored. | | authenticate(context, credentials) | Use the credentials to locate a matching user account. If no account is found, throw a NotFoundError exception. | | establish(context, account) | Establish the token information. | | tokenize(context, data) | Create a token containing the account information. | | respond(context, token, req, res) | Respond back to the client with the token. |

The default extract method extracts parameters user_name and password from the request body. The app only needs to replace this method if different parameters are used.

The default encode method converts the plain text password into a hash using MD5. The configuration param password_salt is used to obfuscate the hash. This generally does not need to be overridden.

No default authenticate method is provided. If this method is not overridden, an error will occur when the handler is invoked. The reason for this is that the framework does not provide any logic to persist user account information. The application must provide this logic.

The tokenize method adds timestamp and expiration info to the session and then signs the object to create the token. There is rarely a reason to replace this method. The method uses configuration params jwt_secret and jwt_expiration.

The respond method returns the token in an object in the form {token: xxxxx}. This only needs to be overridden if a different response is expected.

Example:

Initialization

var login_user = app.handlers.create('login_user', app)
  .authenticate((context, credentials) => {
    ... locate user account and return account object, or throw an error if not found
  });

Invocation

const login = (req, res, next) => {
  login_user(req, res, next);
}

Authentication

Once a user is logged in, the auth token must be passed with each request. By default, it is sent in the HTTP Authorization header. The authentication handler uses this token to establish a session before the app attempts to perform an action.

Since the authentication handler is designed to operate as middleware, the last thing it does is invoke the next method to pass control along.

The authentication handler process consists of these steps, executed in sequence:

| Action | Description | | --- | --- | | extract(context, req, res) | Extract the authentication token from the request. | | parse(context, token) | Parse the token to obtain authentication information and generate a session object. | | inject(context, session) | Inject the session object into the context. |

The default extract method extracts the token from HTTP header Authorization.

The default parse method parses the token per the configuration params.

The default inject method injects the session info into the context object.

It is normally not necessary to override any of these methods.

Example:

Initialization

var authentic_user = app.handlers.create('authentic_user', app);

Routing

var auth = require('../../controllers/auth.js');
var something = require('../../controllers/something');

module.exports = (router, app) => {
  router.route('/something')
    .get(auth.authenticate, something.action);
};

Invocation

const authenticate = (req, res, next) => {
  authentic_user(req, res, next);
}

Registration

The standard account creation requires user name and password. These must be validated to ensure they comply with the requirements - for example, requiring a password have a mix of upper and lower case, special characters, and numbers. The user name must be available.

The registration handler process consists of these steps, executed in sequence:

| Action | Description | | --- | --- | | extract(context, req, res) | Extract the credentials from the request. | | validate(context, credentials) | Ensure the credentials comply with requirements. | | encode(context, credentials) | Encode the password, by default with MD5, to match with the stored one. | | register(context, credentials) | Attempt to create the account with the given credentials. If the user name is already in use, throw a DuplicateError exception. | | respond(context, token, req, res) | Respond back to the client with the new account information. |

The default extract method extracts parameters user_name and password from the request body. The app only needs to replace this method if different parameters are used.

The default validate method ensures the user name and password comply with requirements. If configuration parameter regex_user_name is provided, it is used to perform a regex test on the user name. If configuration parameter regex_password is provided, it is used to perform a regex test on the password. If this is sufficient, this method does not need to be overridden.

The default encode method converts the plain text password into a hash using MD5. The configuration param password_salt is used to obfuscate the hash. This generally does not need to be overridden.

No default register method is provided. If this method is not overridden, an error will occur when the handler is invoked. The application must provide the logic to create the new account with the given credentials, or fail if the user name is not available.

The respond method returns the new account information in an object in the form {account_id: xxxxx}. This only needs to be overridden if a different response is expected.

Example:

Initialization

var register_user = app.handlers.create('register_user', app)
  .register((context, credentials) => {
    ... create the new account, or throw a `DuplicateError` exception if taken
  });

Invocation

const register = (req, res, next) => {
  register_user(req, res, next);
}

Change Password

The standard account creation requires a user name and password. These must be validated to ensure they comply with the requirements - for example, requiring a password have a mix of upper and lower case, special characters and numbers. The user name must be available.

The password change handler process consists of these steps, executed in sequence:

| Action | Description | | --- | --- | | extract(context, req, res) | Extract the password from the request. | | validate(context, credentials) | Ensure the password complies with requirements. | | encode(context, credentials) | Encode the password, by default with MD5, to match with the stored one. | | change(context, credentials) | Change the password in the account. | | respond(context, token, req, res) | Respond back to the client with the new account information. |

The default extract method extracts the password parameter from the request body. The app only needs to replace this method if this is different.

The default validate method ensures the password complies with requirements. If configuration parameter regex_password is provided, it is used to perform a regex test on the password. If this is sufficient, this method does not need to be overridden.

The default encode method converts the plain text password into a hash using MD5. The configuration param password_salt is used to obfuscate the hash. This generally does not need to be overridden.

No default change method is provided. If this method is not overridden, an error will occur when the handler is invoked. The application must provide the logic to update the account with the new hashed password.

The respond method responds with HTTP code OK upon success. This only needs to be overridden if a different response is expected.

Example:

Initialization

var change_user_password = app.handlers.create('change_user_password', app)
  .change((context, credentials) => {
    ... change the existing account with the new password
  });

Invocation

const password = (req, res, next) => {
  change_user_password(req, res, next);
}

Advanced Topics

Alternative Launch Methods

Standard Method

The standard launch of an app requires no server code. It only requires a start script directive in the package.json file:

  "start": "./node_modules/api-accelerate/bin/run.js"

The app can then be launched with the standard command:

npm start

Server Launch Code Alternative

It is possible to launch API Accelerate from your own server. For example:

./server.js

require('api-accelerate')().run();

This code creates an app instance and then runs it.

This can then be launched manually:

node server

Run vs Start

It is also possible to run the server this way:

require('api-accelerate')().start();

When the run method starts the server it also sets an event listener for CTRL-C press, and stops the server gracefully. The start method does not capture CTRL-C, so that the process is halted immediately by the OS.

Alternative Configuration Methods

By default, the server looks for configuration file config.js. However, there are alternative methods. For example, the app can provide a configuration object itself, or pass a JSON string in. This flexibility can be helpful for automated testing, or for implementing systems that start multiple servers.

These are the configuration methods:

| Method | Example | Description | | --- | --- | --- | | Default | require('api-accelerate')().run(); | Looks for the default config.js file. | | Other file | require('api-accelerate')('<file name>.js').run(); | Provides a script that exports config information. | | JSON file | require('api-accelerate')('<file name>.json').run(); | Provides an alternate JSON configuration file. | | JSON string | require('api-accelerate')('<JSON string>').run(); | Provides a JSON string that is parsed. | | Object | require('api-accelerate')(<object>).run(); | Provides an object. Equivalent to a parsed JSON string. |

The server will inspect the parameter and determine if it is a JS file name, a JSON file name, a JSON string, or an object. These alternative methods can be useful for activities such as testing or deployment branching.

Lifecycle Events

An app can listen for lifecycle events and perform additional actions such as starting and stopping custom services or performing additional configuration.

Events:

| Event | Description | | --- | --- | | start | Invoked at the beginning, before the app is configured. | | ready | Invoked once the app is configured and running. | | stop | Invoked when the app is triggered to stop. | | exit | Invoked once the app is stopped and is about to exit. |

Event listeners are registered using the on function. Example:

// test.js

const app = (require('api-accelerate'))();

app.on('start', () => {
  app.logger.info('START');
});

app.on('ready', () => {
  app.logger.info('READY');
// Perform additional configuration, start up other services etc.
});

app.on('stop', () => {
  app.logger.info('STOP');
// Stop other services
});

app.on('exit', () => {
  app.logger.info('EXIT');
});

app.run();

Output:

> node test

info:  START
info:  READY: config = {"sourceUrl":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson","targetFolder":"/var/usr/geologic/data/earthquakes"}
^C
info:  STOP
info:  EXIT
> 

Synchronization

TBD

Multiple Databases

TBD

Sample Projects

A set of sample projects can be found at API Accelerate Demos.

These demonstrate how to use the various features of API Accelerate.