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

azure-monofunction

v1.2.0

Published

Now you can have multiple API routes and controllers using a single Azure Function.

Downloads

14

Readme

Azure Monofunction is a router for Single-Function APIs (SFA) that makes possible for you to develop monolithic APIs and still use the power of serverless, like cost-per-consumption and automatic scalability.

See all features.

Table of contents


Installation

Requirements

Azure Monofunction was tested for the environments below. Even we believe it may works in older versions or other platforms, it is not intended to.

| Environment | Tested version | | ------------------- | ------------------- | | OS | Ubuntu 20.04 | | Node.js | 12.16.3 | | Package Manager | npm 6.14.5 | | Platforms | Azure Functions Host v2 and v3 |

Installing

Via package manager

$ npm install --save azure-monofunction

Configuring the single function

  1. Create a HTTP trigger in you function app. It can have any name, like "monofunction".

  2. Change the route property for the trigger binding in function.json to a wildcard, like {*segments}. You can copy the function.json template.

  3. Create the function index.js at the function folder and start develop following the basic usage below.


Usage

TL;DR

The most simple usage

const app = require('azure-monofunction');

const routes = [{
  path: '/example/:param',
  methods: ['GET', 'PUT'],
  run: async (context) => {
    context.res.body = { it: 'works' };
  },
}]

app.addRoutes(routes);

module.exports = app.listen();

The anatomy of a simple route is an URL path, its HTTP methods to match and some middleware to run.

An alternative to the addRoutes method that supports a list of route objects is the method route that adds a single route to the router.

app.route(['GET','POST'], '/example/path', async (context) => {
  // ...middleware logic
});

Defining route paths

Route paths must always start with / and be followed by valid URL paths.

You can define dynamic parameters prefixing a path level with :.

For example, the URL /users/123 will match the route /users/:id.

The parameter will be available at the context.params object.

Example:

app.route(['GET'], '/users/:id', async (context) => {
  const userId = context.params.id;
});

Defining the HTTP methods for a route

HTTP verbs must always be defined as arrays of strings both in route objects and route method.

The supported HTTP verbs are: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'].

Additionally, you can add routes with verbs corresponding methods: get(), post(), patch(), put(), delete().

app.addRoutes([{ 
  methods: ['GET', 'POST'],
  path,
  run,
}]);

// is the same of:

app.route(['GET', 'POST'], path, run);

// and the same of:

app.get(path, run); // feels like Express 🙂

Using middlewares for routes

Middlewares works with the power of Azure Functions Middlewares and are asynchronous functions that takes a required context argument and an optional STOP_SIGNAL argument.

Request object is available through context.req property and response should be set using the context.res property. Other Azure Functions context properties are also available.

When defining a middleware for a route with the run property/argument, you can use a single middleware function or an array of middlewares.

Example:

const headerMiddleware = async (context) => {
  context.res.headers['X-User-ID'] = context.params.id;
  context.anything = true;
};

const resourceMiddleware = async (context) => {
  context.res.body = { added: context.anything };
};

app.get('/posts/:id', [headerMiddleware, resourceMiddleware]);

// output of /posts request should be

// HTTP 200 /posts/123
// X-User-ID: 123
// { "added": true }

It is strongly recommended you read Azure Functions Middlewares docs, but, if you can't, please note:

  • Always use asynchronous functions as middlewares.
  • Do not return anything inside your middleware function, unless you want to throw an error.
  • You can pass values to the next middlewares using the context object reference.
  • Return the STOP_SIGNAL argument in the middleware if you need to prevent any following middleware to be executed. This is useful for Content-type negotiation or Authorization.
  • See other context-related docs in Accessing and modifying the context at the Azure Functions Middlewares reference.
  • Common community middlewares are available under the Azure Functions Middlewares project.

Controller architecture

Using a single-function API leads you back to the need of middleware reusability and composition.

This monolithic approach is more maintainable than microservices approach, but requires more organization.

When dealing with many routes and its middlewares, you certainly will fall to a controller separation pattern.

Controllers are often separated by resource entities or related services.

It is also good to separate your routes inside a route file for better reading.

Example:

Function directory tree

.
├── controllers
│   └── user.controller.js
├── monofunction
│   ├── function.json
│   ├── index.js
│   └── routes.js
└── host.json

/controllers/user.controller.js


async function getUser({ req, res, params }) {
  context.res.body = await db.getUser(params.id);
}

async function createUser({ req }) {
  context.res.status = await db.createUser(req.body);
}

module.exports = {
  getUser,
  createUser,
};

/monofunction/routes.js

const userController = require('../controllers/user.controller');

module.exports = [{
  path: '/users/:id',
  methods: ['GET'],
  run: userController.getUser,
}, {
  path: '/users',
  methods: ['POST'],
  run: userController.createUser,
}];

/monofunction/index.js

const app = require('azure-monofunction');
const routes = require('./routes');

app.addRoutes(routes);

module.exports = app.listen();

Capturing errors

If a middleware returns a value or throws an error, an error will also be forwarded to Azure Functions Host execution.

If you want to catch this unhandled errors, you can use the onError handler.

app.onError((error, context) => {
  // error argument contains the original value
  // context is preserved until here
});

Global middlewares

Sometimes you need to run common middlewares for all the routes, regardless its resources, just like headers validation, authorization rules and content-type negotiation and parsing.

You can add middlewares for all the routes ("global middlewares") using the use() method.

app.use(async (context) => {
  context.res.headers['X-Powered-By'] = '⛈ Azure Monofunction';
});

app.addRoutes(routes);

// now all the routes will respond with a 'X-Powered-By' header

You can also add a conditional global middleware calling the useIf() method from Azure Functions Middlewares.

const isChromeAgent = (context) => {
  return context.req.headers['User-Agent'].indexOf('Chrome') !== -1;
}

app.useIf(isChromeAgent, async (context) => {
  context.res.headers['Content-Type'] = 'text/html';
});

// now if a route was called from Chrome browser, the response will be set to HTML COntent-Type

Find useful built middlewares in Common community middlewares of Azure Functions Middlewares.

Defining a not found fallback

If no route was matched during a request, Azure Monofunction will throw a not found error.

But if you want to handle this not found event with a fallback route, you can use the on404() handler.

app.on404((context) => {
  // add your fallback logic here, like:
  context.res.status = 404;
});

Route custom metadata

You can add custom route metadata in route object's meta property that will be available in context.meta property:

const routes = [{
  path: '/route',
  methods,
  meta: {
    something: 'value',
  },
  run: async (context) => {
    context.res.body = context.meta;
    // body will be { "something": "value" }
  },
}];

This is useful when you need to recover this meta in other middlewares, specially conditional middlewares, like an authorization middleware:

const hasAuth = (context) => context.meta && context.meta.auth;
app.useIf(hasAuth, async (context, STOP_SIGNAL) => {
  if (!context.req.headers.Authorization) {
    context.res.status = 401;
    return STOP_SIGNAL;
  }
});

app.addRoutes([{
  path: '/resource',
  methods: ['POST'],
  meta: {
    auth: true
  },
  run,
}, {
  path: '/resource',
  methods: ['GET'],
  run,
}]);

// POST /resource without an Authorization header will return HTTP 401
// but GET /resource will not

Debugging and customizing logger

You can log everything that is done by Azure Monofunction setting the debug property to true.

app.debug = true;

You can also use a different logger than console/context setting it in the logger property.

app.logger = MyCustomLogger;

ℹ Note that your logger need to have a log(message, ...args) method.

Route prefix

Azure Functions Host has a route prefix for all the requests. This defaults to /api but you can customize it in host.json:

{
  "extensions": {
    "http": {
      "routePrefix": "api",
    }
  }
}

If you customize the route prefix, Azure Monofunction will try to guess it from extensions.http.routePrefix configuration defined in host.json file.

If in a parallel universe you did not defined the route prefix in host.json but your function app has a route prefix different than /api, you need to specify that in the Azure Monofunction's routePrefix property.

app.routePrefix = '/notapi';

Make sure if you need to change it you did this changing before adding any route.


Extending

Azure Monofunction is not intended to be extensible, but the middleware approach is extensible itself.

If you want to publish a middleware (or an evaluation function) you developed and think it will be useful for any other developer, see Writing and publishing common middlewares in Azure Functions Middlwares.


Help

Support

If you need help or have a problem with this project and you not found you problem in FAQ above, start an issue.

We will not provide a SLA to your issue, so, don't expect it to be answered in a short time.


API

AzureMonofunctionRoute class

Fields

The path should be matched for the route.

The HTTP verbs that should be matched for the route.

A single middleware function or an array of middleware functions.

A middleware should be in form of async function (context, STOP_SIGNAL?):any, as documented in asyncMiddleware specification.

Custom route metadata to be available at context.meta property.

AzureMonoFunction class

Fields

Determines if Azure Monofunctions operations should be logged or not.

The logger object containing a log(message, ...args) function that will be used for logging messages.

The route prefix that will be used in route matching.

Methods

Adds a list of routes to the monofunction router.

Arguments

| Argument | Type | Required | Default | Description | | - | - | - | - | - | | routes | AzureMonofunctionRoute[] | true | | A list of valid routes. |

Adds a single route to the monofunction router for HTTP verb corresponding to the method name.

It is an alias for the method route(), but with the argument methods already defined.

Returns the Azure Functions entrypoint async (context) => {} that will be called by the function HTTP trigger and will execute the entire router.

Returns

AzureFunction: the Azure Functions entrypoint.

Registers an error callback to be called when a middleware throws an error.

Arguments

| Argument | Type | Required | Default | Description | | - | - | - | - | - | | catchCallback | function | true | | A callback function that takes two arguments (error, context) |

Callbacks

catchCallback

function (error, context):any

| Argument | Type | Required | Default | Description | | - | - | - | - | - | | error | Error|any | true | | The error thrown by a middleware | | context | Context | true | | The Azure Function context object. |

Returns: anything returned by the callback will be ignored.

Registers a fallback handler to be called when no route matches the current URL.

Arguments

| Argument | Type | Required | Default | Description | | - | - | - | - | - | | notFoundHandler | function | true | | A callback function that takes one argument (context) |

Callbacks

notFoundHandler

function (context):void

| Argument | Type | Required | Default | Description | | - | - | - | - | - | | context | Context | true | | The Azure Function context object. |

Returns: anything returned by the callback will be ignored.

Adds a single route to the monofunction router.

Arguments

| Argument | Type | Required | Default | Description | | - | - | - | - | - | | methods | Array<'GET'|'POST'|'PATCH'|'PUT'|'DELETE'> | true | | List of HTTP verbs for the route. | | path | string | true | | The URL path for the route. | | middlewares | AsyncGeneratorFunction|AsyncGeneratorFunction[] | true | default | A single function middleware or a list of middlewares that should run for the route, in form of async function (context, STOP_SIGNAL?):any, as documented in asyncMiddleware specification. |

Adds a global middleware to be executed for every matched route.

Identical to use() method from Azure Functions Middlewares.

Adds a global middleware to be conditionally executed for every matched route if the specified expression returns true.

Identical to useIf() method from Azure Functions Middlewares.


Tecnhical concepts

Motivation and design

Azure Monofunction borned to help developers use the power of serverless in Azure Functions but with minimal complexity with function management.

When using serverles, developers often end up with a microservices architecture, even with functions in the same Function App. Each function requires a HTTP binding, your own endpoint and an endpoint maps only to a single resource.

This complexity leads developers to create many triggers and functions to simple resource APIs, like CRUD APIs: you will ever need at least two functions (one for /resource and one for /resource/:id) and at least five if clauses in these functions to make it possible.

Then, in the end you always end up with two options: mix logic and confuse next API maintainers or deal with a lot of functions for basic operations that could be aggregated.

You probably experienced this, as we experienced at NOALVO, and you sure were not satisfied with any of these two approaches.

If you want to keep up with Azure Functions powers, like cost-per-consumption and elastic, automatic scalability, now you can build a monolithic API architecture, just like in Express, Koa or HAPI, using Azure Monofunction.

Azure Monofunction was inspired specially from Express monolithic APIs.

Curiosity: Azure is the cloud ☁, Functions is the flash ⚡, Azure Functions Middlewares is the thunder 🌩 and Azure Monofunction is the thunderstorm ⛈.

Features

  • Multilevel route matching
  • Dynamic route params
  • Route HTTP verbs
  • Route middleware
  • Route metadada
  • Multiple middlewares (cascade) per route
  • Global standard and conditional middlewares
  • Error handler middleware
  • Not found/404 handler middleware

Related projects


Contributing

If you don't want to code

Help us spreading the word or consider making a donation.

Star the project

Tweet it

Add your company name to the Who is using secion

Make a pull request or start an issue to add your company's name.

If you want to code

Code of conduct

We follow Contributor Covenant Code of Conduct. If you want to contribute to this project, you must accept and follow it.

SemVer

This project adheres to Semantic Versioning 2.0.0.

Roadmap

If you are not solving an issue or fixing a bug, you can help developing the roadmap below.

  • [ ] Improve documentation
  • [ ] Conditional middlewares for routes
  • [ ] Async error and not found handlers

Hall of fame

Who is using


License

Licensed under the MIT License.