Skip to content

Commit

Permalink
- New: support AWS lambda
Browse files Browse the repository at this point in the history
  - added check for lamda env, and basic parsing and routing of the lambda event object
  - added middleware support: middleware funcs take `(event, next)`, not `(req, res, next)`
  - everything else is the same as HTTP middleware
- Fix: improved HTTP server support
  - fixed some expressjs API compatibility issues (available methods, order of params, etc)
  - improved handling of middleware:
    - can use `next()`, with error handling
    - can use middleware only for specific routes:
      - `router.use('/admin', loadAdminUI)` - add one middleware func to run if user requested `/admin`
      - `router.use('/admin', [auth, loadAdminUI])` - or add a list of middleware to the specific route
  - parse HTTP body into one string, and then into params object if URL or JSON encoded
- Fix: process `*` in routes patterns better
- Fix: process CLI arguments better (no "routes" needed as first param)
- Updates to `README.md` and `examples/`

...Squashed commit of the following:

commit 42f9c53
Author: sc0ttj <[email protected]>
Date:   Sun Jul 26 12:50:58 2020 +0000

    README fix

commit ca99dc3
Author: sc0ttj <[email protected]>
Date:   Sun Jul 26 12:47:38 2020 +0000

    README fix

commit 7310b4d
Author: sc0ttj <[email protected]>
Date:   Sun Jul 26 12:42:08 2020 +0000

    small improvement to CLI router docs

commit f9c7aad
Author: sc0ttj <[email protected]>
Date:   Wed Jul 22 00:51:18 2020 +0000

    updated lambda example

commit e5152fa
Author: sc0ttj <[email protected]>
Date:   Tue Jul 21 18:03:45 2020 +0000

    clean up: remove logging, add comments

commit bc94c3e
Author: sc0ttj <[email protected]>
Date:   Tue Jul 21 17:16:44 2020 +0000

    fix wildcard routes that end with a "*"

commit 1e0f6c0
Author: sc0ttj <[email protected]>
Date:   Tue Jul 21 13:52:50 2020 +0000

    fix running HTTP server

commit 2cfae8f
Author: sc0ttj <[email protected]>
Date:   Tue Jul 21 01:23:45 2020 +0000

    lambda fix: handle base64 encoded event bodies

commit c92931e
Author: sc0ttj <[email protected]>
Date:   Tue Jul 21 00:04:10 2020 +0000

    make lambdas mroe consistent with HTTP servers: event.body is replaced by parsed JS object version, if it was URL or JSON encoded - same as in Node servers

commit 1d57f07
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 23:02:25 2020 +0000

    README update

commit 86c335c
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 23:00:14 2020 +0000

    small code clean up, small fixes to lambda middleware setup, updated docs

commit 2eed064
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 22:41:54 2020 +0000

    fix middleware setup:
    - do it before processing a route
    - do it for node servers and lambdas
    - run middleware in each lambda request too (not only node servers)

commit 16fb6b9
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 22:22:12 2020 +0000

    README fix

commit ac05793
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 22:19:39 2020 +0000

    README fix

commit 0c49901
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 21:48:51 2020 +0000

    updated README

commit c68381e
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 21:31:58 2020 +0000

    better body handling for lambdas (based on "middy" src code)

commit b390ba8
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 11:04:22 2020 +0000

    dont throw caught middleware errors, just log them out

commit 3d2a14a
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 00:54:59 2020 +0000

    updated README, lambda stuff

commit 4edb5e7
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 00:45:29 2020 +0000

    updated README, lambda stuff

commit ef2d5d3
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 00:44:33 2020 +0000

    updated README, lambda stuff

commit c010cf5
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 00:43:16 2020 +0000

    updated README, lambda stuff

commit a116309
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 00:42:33 2020 +0000

    updated README, lambda stuff

commit 3a2d090
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 00:30:44 2020 +0000

    README fix

commit 4c21c4a
Author: sc0ttj <[email protected]>
Date:   Mon Jul 20 00:20:20 2020 +0000

    new: run middleware only for specific routes:

    router.use("/some-path", myMiddleware)

    router.use("/some-path", [fn1, fn2])

commit 7e8f88d
Author: sc0ttj <[email protected]>
Date:   Sun Jul 19 23:02:10 2020 +0000

    test on all recent Node versions

commit de1f6a3
Author: sc0ttj <[email protected]>
Date:   Sun Jul 19 22:54:51 2020 +0000

    more express-compatible handling of errors thrown by middleware (maybe)

commit ef6e6bc
Author: sc0ttj <[email protected]>
Date:   Sun Jul 19 21:55:13 2020 +0000

    safer body parsing: try to detect if `body-parser` already ran

commit 6967904
Author: sc0ttj <[email protected]>
Date:   Sun Jul 19 20:44:49 2020 +0000

    safer res.jsonp() - stolen from expressjs

commit 596d1fe
Author: sc0ttj <[email protected]>
Date:   Sun Jul 19 20:21:42 2020 +0000

    README update

commit 84b3fde
Author: sc0ttj <[email protected]>
Date:   Sun Jul 19 20:13:51 2020 +0000

    README updates

commit e31291d
Author: sc0ttj <[email protected]>
Date:   Sun Jul 19 19:38:00 2020 +0000

    added Lambda example to README

commit 7a59bfa
Author: sc0ttj <[email protected]>
Date:   Sun Jul 19 19:07:45 2020 +0000

    README updates

commit 5c352bb
Author: sc0ttj <[email protected]>
Date:   Sun Jul 19 18:59:26 2020 +0000

    updated package.json

commit 34511b7
Author: sc0ttj <[email protected]>
Date:   Sun Jul 19 18:56:32 2020 +0000

    new: support lambdas and basic HTTP body parsing:
    - added check for lamda, and basic parsing and routing of event obj
    - improved HTTP server support:
      - fixed some expressjs API compatibility
      - improved handling of middleware, support for using next()
      - parse HTTP body into params object, if URL or JSON encoded
    - updates to README and examples/
  • Loading branch information
sc0ttj committed Jul 26, 2020
1 parent 1f31ede commit e762a06
Show file tree
Hide file tree
Showing 7 changed files with 548 additions and 99 deletions.
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
language: node_js
sudo: false
node_js:
- 14
- 13
- 12
- 11
- 10
- 9
- 8
Expand Down
206 changes: 179 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@

**router** resolves URL paths like **/profile/1** to route patterns such as **/profile/:id**, and generates a params object that is passed to each route.

**router** also provides some basic web server features if routing HTTP requests - parses `req.body` into params, uses expressjs-like API, supports middleware.

## Features

- Easy setup, zero dependencies
- Only 1kb minified and gzipped
- Only 1.7kb minified and gzipped
- Simple syntax and usage
- Works **client-side**, in browsers:
- as a router for single page applications (SPAs)
- Works **server-side**, in Node:
- as a router for an HTTP server (express.js like API, also supports "middleware")
- as a router for a command-line tool (accepts first arg as the URL/path)
- as a router in an AWS Lambda (routing of the `event` data passed into Lambda)
- Works **in the terminal** as an args parser for command-line programs

## Basic syntax example

Expand Down Expand Up @@ -109,19 +112,9 @@ See the full example in [examples/client-side-router.html](examples/client-side-

## Usage in NodeJS: as a HTTP web server

You _could_ simply put **router** inside a standard NodeJS HTTP server and use `res.write()` and `res.end()` as normal (see this nice guide to the [NodeJS `http` module](http://zetcode.com/javascript/http/)).

However, **router** provides a simple wrapper around these methods, called `res.send()` (just like express.js).

The `res.send()` method makes life easier for you:
You _could_ simply use `router` inside a standard NodeJS `http` server, with it's provided methods `res.writeHead()`, `res.write()` and `res.end()` (see this nice guide to the [NodeJS `http` module](http://zetcode.com/javascript/http/)).

- sets appropriate header status to 200 (if `res.status()` not used)
- sets appropriate content type:
* text/html - if given a string
* application/json - if given an object, array or JSON
* application/octet-stream - if given a Buffer
- pretty prints JSON output
- calls `res.end()` for you
However, `router` provides some simple wrappers around these methods, just like [express.js](https://expressjs.com/en/api.html#res.send).

Here's an example of routing HTTP requests in your NodeJS based web server:

Expand All @@ -143,52 +136,176 @@ http.createServer((req, res) => {
"/user/:userId": params => {
console.log("serving JSON!", params)
// set header to "200" manually
res.status(200)
// set content-type to "application/json",
// set content (prettified JSON)
// end response
res.status(200)
res.send(params)
},
// any other route
"*": params => {
res.send("<h1>API Docs:</h1>")
}

},
// for servers, you must pass in 'res' and 'req', after the routes object above
res, req
// for servers, you must pass in 'req' and 'res', after the routes object above
req, res
)

})

```

### Using middleware
There is a `res.status()` method, which sets `res.statusCode` for you.

There is a `res.send()` method, which makes life easier for you:

- sets appropriate header status to 200 (if `res.status()` not used)
- sets appropriate content type:
* `text/html` - if given a string
* `application/json` - if given an object, array or JSON
* `application/octet-stream` - if given a Buffer
- pretty prints JSON output
- calls `res.end()` for you

The `res.json()` method is similar to above, but sends the Content-Type `application/json`.

The `res.jsonp()` is similar to `res.json()`, but sends the Content-Type `text/javascript` and wraps your JSON in a callback, like so:

```js
callback({ some: \"data\" })
```
### Using HTTP "middleware"
If running an HTTP server (or Lambda, see below), `router` supports "middleware", in a similar way to express.js.
If running an HTTP server, **router** supports "middleware", in a similar way to express.js.
Some [express middleware](https://expressjs.com/en/resources/middleware.html) may work with `router`, though this has not been tested.
Using middleware is very simple - define a function that takes `(res, req)` as parameters:
Creating middleware for `router` is very simple - define a function that takes `(req, res, next)` as parameters:
```js
var getRequestTime = function(res, req) {
var getRequestTime = function(req, res, next) {
req.time = Date.now()
console.log("middleware: added req.time: ", req.time)
next()
}
```
And use `router.use(someFunc)` to enable it:
And do `router.use(someFunc)` to enable it:
```js
// pass the middleware function to router.use()
router.use(getRequestTime)
```
Or enable middleware for specific routes:
```js
router.use("/home", getRequestTime)
```
You can also pass an array of middlewares:
```js
router.use([func1, func2, func3])
```
Or any array of middlewares to run on a specific route:
```js
router.use('/home', [func1, func2, func3])
```
See the full example in [examples/http-router.js](examples/http-router.js)
### About HTTP request `body` parsing:
In NodeJS HTTP servers, the [HTTP request "body" is received in "chunks"](https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/) - you must normally [combine & parse these chunks](https://stackoverflow.com/questions/28718887/node-js-http-request-how-to-detect-response-body-encoding) in order to get access to the whole `req.body` data.
So to make life easier, `router` does this basic parsing of the HTTP request `body` for you, so that it's readily available in the `params` passed to your routes:
1. The `req.body` chunks received are combined into a string, available as `req.body` in your routes.
2. If `req.body` is a URL-encoded or JSON-encoded string, `router` will convert it to a JS object, and also add its _properties_ to `params`. For example, the original `req.body` may be `?user=bob&id=1` - this will be parsed for you and available as `params.user` and `params.id`.
Therefore, when inside your routes, there's often no need to parse `req.body` yourself - unless handling gzipped data or file uploads (multi-part form data or octect-streams). In this case, you should use middleware like [`body-parser`](https://expressjs.com/resources/middleware/body-parser.html).
If you're running a GET-based restful API, you probably don't need to worry about `req.body`, it's usually only for POST data and file uploads.
## Usage in AWS Lambda: as router for your API
Here's an example of using `router` in an AWS Lambda:
```js
'use strict';
var router = require("@scottjarvis/router")
exports.handler = (event, context, callback) => {
router(
{
"/ping": params => {
// do stuff here
callback(null, params)
},
"/user/:userId": params => {
// do stuff here
var resp = { ... }
callback(null, resp)
}
},
// for Lambdas, you must pass in 'event', 'context' and 'callback'
event,
context,
callback
)
}
```
In Lambdas, `router` works out which route to run from the `event.path` property (not from any HTTP `req` objects).
To make life easier inside your routes:
1. If `event.body` is URL-encoded or JSON-encoded, it'll be parsed into a JavaScript object.
2. If `event.body` was parsed into a JavaScript object, its properties will be added into `params`.
3. The `params` object should have everything needed for a valid response object, so it can be passed straight to `callback()` (or returned, if running in an `async` Lambda).
### Using Lambda "middleware"
There is currently very basic middleware support for Lambdas:
- Lambda middleware functions take `(event, next)` as parameters
- so you should read or modify `event`, instead of `req` and `res`
- it should otherwise be similar to using HTTP middleware :)
Here's how to define some Lambda middleware:
```js
var getRequestTime = function(event, next) {
event.time = Date.now()
console.log("middleware: added event.time: ", event.time)
next()
}
```
And just pass the middleware function to `router.use()`:
```js
router.use(getRequestTime)
```
If you need a more advanced Lambda router, see [middy](https://github.com/middyjs/middy).
See the full example in [examples/lambda-router.js](examples/lambda-router.js).
## Usage in NodeJS: as a CLI args parser
If you building a NodeJS program, you might want an easy way to parse the command line arguments it receives.
If so, `router` can help - it auto maps command-line arguments to the `params` object received by your routes:
```
node some_script.js /profile/99 --foo=bar
node some_script.js --foo=bar --verbose --dir=/home
```
will be matched in `some_script.js` by using something like:
Expand All @@ -202,9 +319,10 @@ router({
// 'params' will contain all command-line arguments
// that were passed to this script
"/profile/:id": params => {
console.log(params)
"*": params => {
console.log(params) // { foo: "bar", verbose: true, dir: "/home" }
}
})
```
Expand All @@ -219,16 +337,50 @@ Rebuild the bundles in `dist/` using this command: `npm run build`
## Related projects:
### Alternative routers
- [director](https://github.com/flatiron/director) - a fairly small, isomorphic URL router for JavaScript
- [hasher](https://github.com/narirou/hasher) - Tiny hashchange router inspired by express.js & page.js
- [routie](https://github.com/jgallen23/routie) - a tiny javascript hash router
- [trouter](https://github.com/lukeed/trouter/) - a fast, small-but-mighty, familiar router
- [RouterRouter](https://github.com/jgarber623/RouterRouter) - a tiny JS router, extracted from Backbone's Router
- [gcpantazis/router.js](https://gist.github.com/gcpantazis/5631831) - a very simple router based on BackboneJS Router
- [expressJS](https://expressjs.com/en/) - the most widley used JavaScript router
### Alternative HTTP servers
- [expressJS](https://expressjs.com/en/) - the most widely used JavaScript server, with routing "middleware"
- [polka](https://github.com/lukeed/polka) - minimal, performant expressjs alternative (uses trouter)
- [middy](https://github.com/middyjs/middy) - a popular router for AWS Lambda, uses a middleware-style API
## Acknowledgements
- [Zetcode: javascript/http](http://zetcode.com/javascript/http/)
- [@pyaesonekhant1234: differences-between-res-write-res-end-and-res-send](https://medium.com/@pyaesonekhant1234/differences-between-res-write-res-end-and-res-send-in-node-js-7c29e8e50654)
- [Okta.com: Build and understand express middleware through examples](https://developer.okta.com/blog/2018/09/13/build-and-understand-express-middleware-through-examples)
- [Towards Data Science: Building your own router for AWS Lambda](https://towardsdatascience.com/serverless-building-your-own-router-c2ca3071b2ec)
- [vkhazin/aws-lambda-http-router](https://github.com/vkhazin/aws-lambda-http-router)
## Further improvements
### "express-compat" middleware
A middleware that adds the same properties and methods to `req` and `res` as express.js.
This should improve compatibility with other express.js middleware that is loaded after.
### Basic auth middleware
See
- https://github.com/jshttp/basic-auth
- https://github.com/arkerone/api-key-auth
### JSWT auth middleware
See https://github.com/auth0/node-jsonwebtoken
### Error handling middleware
See https://github.com/expressjs/api-error-handler
2 changes: 1 addition & 1 deletion examples/cli-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ if (typeof process !== "undefined" && process.argv) {
router({
// 'params' will contain all command-line arguments
// that were passed to this script
"/profile/:id": params => {
"*": params => {
console.log(params)
}
})
Expand Down
Loading

0 comments on commit e762a06

Please sign in to comment.