A middleware library from decoupling business logic in Microsoft Azure Functions
MIT License
throw
httpError()
or anything or context.done()
Azure functions is making it a flash to creating an API endpoint. But that's just the infrastructure part. It doesn't mean your business logic can be simplified.
before
and after
stage in one function.Middleware is for decoupling logic. I learned the value of beforeHooks
and afterHooks
after adopting Feathers.JS. Which has a beautiful concept of 3 layers for every endpoint, and I found myself start the declarative programming for the backend. No more code for the repeating work. In micro-azure-functions
's context, it is just an array of Middleware
.
Let's say a simple return-a-user endpoint, what does it look like when you are using micro-azure-functions
import { azureFunctions } from 'micro-azure-functions';
const handler = azureFunctions([
validateRequestBody(GetUserSchema),
isStillEmployed,
verifyPaymentStatus,
justReturnUserObjectDirectlyFromDB,
removeFieldsFromResponse('password', 'address'),
combineUserNames,
transformResponseToClientSideStructure,
]);
Ideally, you can just compose your future Azure functions without writing any code except for an integration test. The logic will be declarative. Every middleware here can be fully tested and ready to reuse.
npm install micro-azure-functions
import { azureFunctions } from 'micro-azure-functions';
const handler = azureFunctions([
context => {
context.res = { status: 200, body: { message: 'it works' } };
},
]);
// call the API, you will get json response: { message: "it works" }
export type Middleware<PassDownObjType = any> = (
context: MiddlewareContext<PassDownObjType>,
...args: any[]
) => Promise<void> | void;
This is 99.999% identical with the AzureFunctions type from @azure/functions
, but one thing, it appends a new property to Context named passDownObj
, which is used to shared value among middlewares.
How to control the flow?
context.done
will STOP the executionthrow
will STOP the executionMiddleware
will just be executed one by oneHow can I return
Azure
way, setup context.res
success(), badRequest(), internalRequest()
, so you can do context.res = success()
, just setup the statusCode for yousuccess()
(just a httpResponse()
with status code set to 200, you can still change it)What can you throw
httpError()
badRequest()
internalError()
How to check what will be returned as the Http response
context.res
How to change the response
context.res
How to pass something down the chain,
context.passDownObj
context.passDownObj.myValue = 123
, myValue
could be any nameThere are 2 types of response:
httpError()
for throw
httpResponse()
for return
success()
badRequest()
internalRequest()
throw
a plain object
| string
| number
=== (400) responsestatusCode
propertyThe built-in
one has some shortcuts to use.
All parameters are customizable.
import { httpError, httpResponse } from 'micro-azure-functions';
// It gives you an instance of HttpError, which extends from Error
const error = httpError({
// default status code is 400 if not set
status: 401,
body: {
message: 'test',
},
headers: {
'x-http-header': 'fake-header',
},
});
// It gives you a plain JS object.
const response = httpResponse({
// default status code is 200 if not set
status: 200,
body: {
message: 'test',
},
headers: {
'x-http-header': 'fake-header',
},
});
The commons headers are:
Compare to the above methods, the only difference is the shortcuts just sets the status code, you can still modify them if you want.
httpError
:
badRequest()
: 400internalRequest()
: 500httpResponse
:
success()
: 200It will context.log
any error caught at the very top level
azureFunctions([], { logError: true });
It will context.log
context.req
:
azureFunctions([], { logRequest: true });
In the following case, if the request name is 'albert', only validateRequest
will be called.
import { badRequest, Middleware } from 'micro-azure-functions';
const validateRequest: Middleware = (context, req) => {
if (req.body.name === 'albert') {
throw badRequest({
message: 'bad user, bye bye',
});
}
};
// it will return a 400 error { message: 'bad user, bye bye' }
Or if you like me, you can write a simple validating middleware with the yup
schema, you can then reuse from the client side.
import { Schema } from 'yup';
import { azureFunctions, Middleware, badRequest } from 'micro-azure-functions';
const validateBodyWithYupSchema = (schema: Schema): Middleware => async ({
event,
}) => {
if (!schema.isValid(event.body)) {
throw badRequest('bad request');
}
};
const handler = azureFunctions([validateBodyWithYupSchema(myYupSchema)]);
import { badRequest } from 'micro-azure-functions';
const removeFieldsFromResponse = (fieldsToRemove: string[]): Middleware = (context) => {
const newResponse = Object.assign({}, response);
fieldsToRemove.forEach(field => {
if (context.res[field] != null) {
delete context.res[field]
}
})
return newResponse;
};
const testHandler = azureFunctions(
[
(context) => {
context.res = {
status: 200,
body: {
name: 'albert',
password: '123qwe',
address: 'somewhere on earth'
}
}
},
removeFieldsFromResponse(['password', 'address'])
],
);
// response will be { name: 'albert' }