Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/leaonline/http-factory
Create Meteor connect HTTP middleware. Lightweight. Simple.
https://github.com/leaonline/http-factory
factory hacktoberfest http routes webapp
Last synced: about 3 hours ago
JSON representation
Create Meteor connect HTTP middleware. Lightweight. Simple.
- Host: GitHub
- URL: https://github.com/leaonline/http-factory
- Owner: leaonline
- License: mit
- Created: 2020-05-03T14:02:19.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2022-03-16T10:18:12.000Z (almost 3 years ago)
- Last Synced: 2024-11-15T16:12:29.842Z (2 months ago)
- Topics: factory, hacktoberfest, http, routes, webapp
- Language: JavaScript
- Homepage:
- Size: 21.5 KB
- Stars: 1
- Watchers: 4
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Meteor HTTP Factory
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)
![GitHub file size in bytes](https://img.shields.io/github/size/leaonline/http-factory/http-factory.js)
![GitHub](https://img.shields.io/github/license/leaonline/http-factory)Create Meteor `WebApp` (connect) HTTP middleware. Lightweight. Simple.
With this package you can define factory functions to create a variety of Meteor HTTP routes.
Decouples definition from instantiation (also for the schema) and allows different configurations for different
types of HTTP routes.**Minified size < 2KB!**
## Table of Contents
- [Why do I want this?](#why-do-i-want-this)
- [Installation](#installation)
- [Usage](#usage)
- [Basic example](#basic-example)
- [Use `WebApp.rawConnectHandlers`](#use-webapprawconnecthandlers)
- [Create universal handlers](#create-universal-handlers)
- [Specify a method](#specify-a-method)
- [Passing data to the next handler](#passing-data-to-the-next-handler)
- [Responding with errors](#responding-with-errors)
- [Throwing 500 errors](#throwing-500-errors)
- [Handle custom error responses](#handle-custom-error-responses)
- [With schema](#with-schema)
- [Using SimpleSchema](#using-simpleschema)
- [Overriding `validate` when using schema](#overriding-validate-when-using-schema)
- [Using check](#using-check)
- [Using middleware](#using-middleware)
- [Define global middleware](#define-global-middleware)
- [Define route-specific middleware](#define-route-specific-middleware)
- [Define middleware using the internal environment](#define-middleware-using-the-internal-environment)
- [Codestyle](#codestyle)
- [via npm](#via-npm)
- [via Meteor npm](#via-meteor-npm)
- [Test](#test)
- [Watch mode](#watch-mode)
- [Changelog](#changelog)
- [License](#license)## Why do I want this?
- Decouple definition from instantiation
- Easy management between own and externally defined middleware on a local or global level
- Validate http request arguments (query/body) the same way as you do with `mdg:validated-method`
- Just pass in the schema as plain object, instead of manually instantiating `SimpleSchema`
- Easy builtin reponse schema, allowing you to either return a value (to create 200 responses) or throw an Error
(for 500 responses). You can still customize responses via `req`, `res` and `next`.
- Easy data access and update between handlers using `this.data()`## Installation
Simply add this package to your meteor packages
```bash
$ meteor add leaonline:http-factory
```## Usage
Import the `createHTTPFactory` function and create the factory function from it.
The factory function can obtain the following arguments (*=optional):- `path: String*`
- `schema: Object*` - depends on, if `schemaFactory` is defined
- `method: String*` - if defined, one of `['get', 'head', 'post', 'put', 'delete', 'options', 'trace', 'patch']`
- `validate: Function*` - if defined, a validation function that should throw an Error if validation fails
- `run: Function` - always required, the middleware handler to run on the current request### Basic example
To make life easier for you, the requests' `query` or `body` data is wrapped before the `run` call into a universal
object. No need to directly access `req.query` or `req.body` and check for properties.
You can instead use the `function` environment's `data` method:```javascript
import { createHTTPFactory } from 'meteor/leaonline:http-factory'
const createHttpRoute = createHTTPFactory() // default, no paramscreateHttpRoute({
path: '/greetings',
run: function (/* req, res, next */) {
const { name } = this.data() // use this to get the current query/body data
return `Hello, ${name}`
}
})
```This code creates a http route, that is handled on any incoming HTTP request (`get`, `post` etc.) and assumes either in query
or on body (depending on request type) to find a parameter, named `name`. Try it via the following client code:```javascript
import { HTTP } from 'meteor/http'HTTP.get('/greetings', { params: { name: 'Ada' }}, (err, res) => {
console.log(res.content) // 'Hello, Ada'
})
```### Use `WebApp.rawConnectHandlers`
If you need to define handlers before any other handler, just pass in the `raw` option:
```javascript
import { createHTTPFactory } from 'meteor/leaonline:http-factory'
const createHttpRoute = createHTTPFactory() // default, no paramscreateHttpRoute({
raw: true,
path: '/greetings',
run: function (/* req, res, next */) {
const { name } = this.data()
return `Hello, ${name}`
}
})
```### Create universal handlers
You can omit `path` on order to run the handler at the root level. This is often used for
middleware like `cors`.### Specify a method
If you specify a HTTP method (one of `['get', 'head', 'post', 'put', 'delete', 'options', 'trace', 'patch']`) your
request will only be handled with the correct request method:```javascript
import { WebApp } from 'meteor/webapp'
import { createHTTPFactory } from 'meteor/leaonline:http-factory'
import bodyParser from 'body-parser'WebApp.connectHandlers.urlEncoded(bodyParser /*, options */) // inject body parser
const createHttpRoute = createHTTPFactory() // default, no params
createHttpRoute({
path: '/greetings',
method: 'post',
run: function (/* req, res, next */) {
const { name } = this.data()
return `Hello, ${name}`
}
})
```The `data` will now contain the `body` data. Note, that you may need to install npm `body-parser` to
work with body content, that is not form data encoded.### Passing data to the next handler
We also made updating data much easier for you. You can pass an `Object` to the `this.data()` method in order to
attach new properties to a request or update existsing ones:```javascript
import { WebApp } from 'meteor/webapp'
import { createHTTPFactory } from 'meteor/leaonline:http-factory'const createHttpRoute = createHTTPFactory() // default, no params
createHttpRoute({
path: '/greetings',
method: 'get',
run: function (req, res, next ) {
const { name } = this.data()
const updateData = {}
if (name === 'Ada') {
updateData.title = 'Mrs.'
}
if (name === 'Bob') {
updateData.title = 'Mr.'
}
this.data(updateData)
next()
}
})createHttpRoute({
path: '/greetings',
method: 'get',
run: function (/* req, res, next */) {
const { name, title } = this.data()
return `Hello, ${title} ${name}`
}
})
```If you call the route, it will contain now the updated data:
```javascript
import { HTTP } from 'meteor/http'HTTP.get('/greetings', { params: { name: 'Ada' }}, (err, res) => {
console.log(res.content) // 'Hello, Mrs. Ada'
})HTTP.get('/greetings', { params: { name: 'Bob' }}, (err, res) => {
console.log(res.content) // 'Hello, Mr. Ada'
})
```## Responding with errors
If a requests is intended to return a fail / error response (400/500 types) you may use our simple solutions, that cover
most of the cases, while ensuring your `run` code contains **logic** and not response handling.### Throwing 500 errors
If your `run` method is throwing an Error, then it will be catched and transformed to a `500`response:
```javascript
import { createHTTPFactory } from 'meteor/leaonline:http-factory'
const createHttpRoute = createHTTPFactory() // default, no paramscreateHttpRoute({
path: '/greetings',
run: function (/* req, res, next */) {
const { name } = this.data()
if (!name) throw new Error('Expected name')
return `Hello, ${name}`
}
})
```The `err` param in the callback will then not be `null` but contain the error response:
```javascript
import { HTTP } from 'meteor/http'HTTP.get('/greetings', {}, (err, res) => {
const error = err.response
console.log(error.statusCode) // 500
console.log(error.data.title) // 'Internal Server Error'
console.log(error.data.description) // 'An unintended error occurred.'
console.log(error.data.info) // Expected name
})
```### Handle custom error responses
If you have a custom error response to return, you can use the builtin `this.handleError` method:
```javascript
import { createHTTPFactory } from 'meteor/leaonline:http-factory'
const createHttpRoute = createHTTPFactory() // default, no paramscreateHttpRoute({
path: '/greetings',
run: function (req, res, next) {
const data = this.data()
if (!data.name) {
return this.error({
code: 400,
title: 'Bad Request',
description: 'Malformed query or body.'
})
}
return `Hello, ${data.name}`
}
})
```## With schema
In order to take the burden of input validation from you, we have added a nice `schema` validation mechanism.
It works similar to the way `mdg:validated-method`.We support various ways to validate an input schema. To **decouple** schema definition from instantiation, we introduced a `shemaFactory`, which
is basically a function that creates your schema for this collection. This also ensures, that
different HTTP routes don't share the same schema instances.#### Using SimpleSchema
```javascript
import { createHTTPFactory } from 'meteor/leaonline:http-factory'
import SimpleSchema from 'simpl-schema'const schemaFactory = definitions => new SimpleSchema(definitions)
const createHttpRoute = createHTTPFactory({ schemaFactory })createHttpRoute({
path: '/greetings',
schema: {
name: String
},
run: function (req, res, next) {
const { name } = this.data()
return `Hello, ${name}`
}
})
```Call the method via
```javascript
HTTP.get('/greetings', { params: { name: 'Ada' }}, (err, res) => {
console.log(res.content) // 'Hello, Ada'
})
```provoke a fail via
```javascript
HTTP.get('/greetings', (err, res) => {
const error = err.response
console.log(error.statusCode) // 400
console.log(error.data.title) // 'Bad request'
console.log(error.data.description) // 'Malformed query or body.'
console.log(error.data.info) // Name is required <-- SimpleSchema error message
})
```#### Overriding `validate` when using schema
You can also override the internal `validate` when using `schema` by passing a `validate` function.
This, however, disables the schema validation and is then your responsibility:```javascript
import { createHTTPFactory } from 'meteor/leaonline:http-factory'
import SimpleSchema from 'simpl-schema'const schemaFactory = definitions => new SimpleSchema(definitions)
const createHttpRoute = createHTTPFactory({ schemaFactory })createHttpRoute({
path: '/greetings',
schema: {
name: String
},
validate: () => {},
run: function (/* req, res, next */) {
const { name } = this.data()
return `Hello, ${name}`
}
})
```and then call via
```javascript
HTTP.get('/greetings', (err, res) => {
console.log(res.content) // 'Hello, undefined'
})
```If none of these cover your use case, you can still create your own validation middleware.
#### Using check
You can also use Meteor's builtin `check` and `Match` for schema validation:
```javascript
import { check } from 'meteor/check'
import { MyCollection } from '/path/to/MyCollection'
import { createHTTPFactory } from 'meteor/leaonline:http-factory'const schemaFactory = schema => ({
validate (args) {
check(args, schema)
}
})const createHttpRoute = createHTTPFactory({ schemaFactory })
createHttpRoute({
path: '/greetings',
schema: {
name: String
},
run: function (/* req, res, next */) {
const { name } = this.data()
return `Hello, ${name}`
}
})
```Note, that some definitions for `SimpleSchema` and `check`/`Match` may differ.
## Using middleware
Often you need to use third-party middle ware, such as `cors` or `jwt`. This package makes it
super easy to do so.### Define global middleware
First, you can define global middleware that is not bound to the factory environment,
which allows for highest compatibility.
Just define it with a property name, that is not one of `schemaFactory, raw`:```javascript
import { Meteor } from 'meteor/meteor'
import { createHTTPFactory } from 'meteor/leaonline:http-factory'// is is just some simple example validation
// of non-standard a-auth-token header
const isValidToken = req => req.headers['x-auth-token'] === Meteor.settings.xAuthToken
const simpleAuthExternal = function (req, res, next) {
if (!isValidToken(req)) {
// external middleware is neither bound to the environment
// nor affected in any way, so it can 100% maintin it's logic
// however, this.error is not available here
const body = JSON.stringify({ title: 'Permission Denied' })
res.writeHead(403, { 'Content-Type': 'application/json' })
res.end(body)
}
next()
}// pass in this middleware on the abstract factory level
// to make all routes of all methods to use this
// additionally, use raw: true in order to ensure this is
// run at the very first, before any package-level handlers
const createHttpRoute = createHTTPFactory({
simpleAuth: simpleAuthExternal,
raw: true
})createHttpRoute({
path: '/greetings',
method: 'get',
run: function () {
const { name } = this.data()
return `Hello, ${name}`
}
})
```now your requests will run through this middleware:
```javascript
HTTP.get('/greetings', (err, res) => {
const error = err.response
console.log(error.statusCode) // 403
console.log(errpr.data.title) // 'Permission Denid'
})const params = { name: 'Ada' }
const headers = { 'x-auth-token': Meteor.settings.xAuthToken } // warning: passing secrets to the client is unsafe
HTTP.get('/greetings', { params, headers }, (err, res) => {
console.log(res.content) // Hello, Ada
})
```### Define route-specific middleware
You can also define external middleware on a specific route without affecting other routes.
Just define it with a property name, that is not one of `path, schema, method, run, validate`:```javascript
import { Meteor } from 'meteor/meteor'
import { createHTTPFactory } from 'meteor/leaonline:http-factory'
import { simpleAuthExternal } from '/path/to/simpleAuthExternal'const createHttpRoute = createHTTPFactory()
createHttpRoute({
path: '/greetings',
simpleAuth: simpleAuthExternal,
method: 'get',
run: function () {
const { name } = this.data()
return `Hello, ${name}`
}
})
```It will work only on this route with this method, other routes won't be affected.
### Define middleware using the internal environment
This becomes a bit redundant, but if you like to run middlware using the internal enviroment,
you need to place as the `run` method:```javascript
import { Meteor } from 'meteor/meteor'
import { createHTTPFactory } from 'meteor/leaonline:http-factory'const createHttpRoute = createHTTPFactory()
// is is just some simple example validation
// of non-standard a-auth-token header
const isValidToken = req => req.headers['x-auth-token'] === Meteor.settings.xAuthToken
const simpleAuthInternal = function (req, res, next) {
if (!isValidToken(req)) {
// internally defined middleware can make use of the environment
return this.error({
code: 403,
title: 'Permission Denied'
})
}
next()
}createHttpRoute({
path: '/greetings',
method: 'get',
run: simpleAuthInternal
})
createHttpRoute({
path: '/greetings',
method: 'get',
run: function () {
const { name } = this.data()
return `Hello, ${name}`
}
})
```### Hooks
You can currently only hook into error handling:
```javascript
import { Meteor } from 'meteor/meteor'
import { createHTTPFactory } from 'meteor/leaonline:http-factory'const createHttpRoute = createHTTPFactory({
// global error hook for all routes
onError: e => {
MyCoolLogger.error(e)
}
})createHttpRoute({
path: '/greetings',
method: 'get',
run: simpleAuthInternal,
// local error hook only for this route, note
// that this route will now use only this hook and not
// the global hook, if such is defined
onError: e => {
MyCoolLogger.error(e)
}
})
createHttpRoute({
path: '/greetings',
method: 'get',
run: function () {
const { name } = this.data()
return `Hello, ${name}`
}
})
```## Codestyle
We use `standard` as code style and for linting.
##### via npm
```bash
$ npm install --global standard snazzy
$ standard | snazzy
```##### via Meteor npm
```bash
$ meteor npm install --global standard snazzy
$ standard | snazzy
```## Test
We use `meteortesting:mocha` to run our tests on the package.
##### Watch mode
```bash
$ TEST_WATCH=1 TEST_CLIENT=0 meteor test-packages ./ --driver-package meteortesting:mocha
```## Changelog
- **1.1.0**
- fix: tests when run method returns undefined values or no value (=undefined)
- feature: `onError` hook can be attached to global factory and factories
- **1.0.1**
- use `EJSON` to stringify results in order to comply with any formats, that
can be resolved via EJSON## License
MIT, see [LICENSE](./LICENSE)