Skip to content

Commit

Permalink
feat(middleware): Firm up the middleware interface
Browse files Browse the repository at this point in the history
  • Loading branch information
vinsonchuong committed Oct 29, 2020
1 parent ce03d5c commit dfb8a82
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 1 deletion.
137 changes: 137 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,143 @@ by running
yarn add passing-notes
```

## Concepts
`passing-notes` provides an interface for building HTTP servers. At its core,
it takes a function that takes in request data and returns response data.

```js
// server.mjs

export default function(request) {
// request = {
// version: '2.0',
// method: 'GET',
// url: '/',
// headers: {},
// body: ''
// }

return {
status: 200,
headers: {
'content-type': 'text/plain'
},
body: 'Hello World!'
}
}
```

This code can be run either from the command line:

```sh
yarn pass-notes server.mjs
```

Or from JavaScript:

```js
import {startServer} from 'passing-notes'
import handleRequest from './server.mjs'

startServer({port: 8080}, handleRequest)
```

### Middleware
Taking cues from popular tools like Express, we encourage organizing your
request-handling logic into middleware:

```js
import {compose} from 'passing-notes'

export default compose(
(next) => (request) => {
const response = next(request)
return {
...response,
headers: {
...response.headers,
'content-type': 'application/json'
},
body: JSON.stringify(response.body)
}
},
(next) => (request) => {
return {
status: 200,
headers: {},
body: {
message: 'A serializable object'
}
}
}
)
```

Each request is passed from top to bottom until one of the middleware returns a
response. That response then moves up and is ultimately sent to the client. In
this way, each middleware is given a chance to process and modify the request
and response data.

Note that one of the middleware must return a response, otherwise, an `Error` is
thrown and translated into a `500` response.

### Developer Affordances
When using the `pass-notes` CLI tool, during development (when
`NODE_ENV !== 'production'`), additional features are provided:

#### Hot Reloading
The provided module and its dependencies are watched for changes and re-imported
before each request. Changes to your code automatically take effect without you
needing to restart the process.

The `node_modules` directory, however, is not monitored due to its size.

#### Self-Signed Certificate
HTTPS is automatically supported for `localhost` with a self-signed certificate.
This is needed for browsers to use HTTP/2.0 when making requests to the server.

### Logging
By default, the method and URL of each request and the status of the response is
logged to `STDOUT`, alongside a timestamp and how long it took to return the
response.

To log additional information:

```js
import {Logger} from 'passing-notes'

export const logger = new Logger()

export default function(request) {
logger.log({
level: 'INFO',
topic: 'App',
message: 'A user did a thing'
})

// ...
}
```

In addition, our `Logger` provides a way to log the runtime for expensive tasks,
like database queries:

```js
const finish = logger.measure({
level: 'INFO',
topic: 'DB',
message: 'Starting DB Query'
})

// Perform DB Query

finish({
message: 'Finished'
})
```

The logger can be passed to any middleware that needs it as an argument.

## API

### `pass-notes server.js`
Expand Down
1 change: 0 additions & 1 deletion cli/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ test('logging requests', async (t) => {
t,
{PORT: '11005'},
`
export default function (request) {
if (request.url === '/error') {
throw new Error('Bad')
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './http/index.js'
export * from './middleware/index.js'
export {default as Logger} from './logger.js'
7 changes: 7 additions & 0 deletions middleware/compose.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import flowRight from 'lodash/flowRight.js'

export default function (...middlewares) {
return flowRight(...middlewares)(() => {
throw new Error('Unhandled Request')
})
}
60 changes: 60 additions & 0 deletions middleware/compose.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import test from 'ava'
import {startServer, stopServer, sendRequest, compose} from '../index.js'

test('composing middleware', async (t) => {
const server = await startServer(
{port: 10003},
compose(
(next) => async (request) => {
const response = await next({
...request,
headers: {
...request.headers,
trace: '1'
}
})

t.is(response.headers.trace, '2')

return {
...response,
headers: {
...response.headers,
trace: `${response.headers.trace}1`
}
}
},
() => (request) => {
t.is(request.headers.trace, '1')

return {
status: 200,
headers: {
trace: '2',
'content-type': 'text/plain'
},
body: 'Hello World!'
}
},
() => () => ({status: 404, headers: {}, body: ''})
)
)
t.teardown(async () => {
await stopServer(server)
})

t.like(
await sendRequest({
method: 'GET',
url: 'http://localhost:10003',
headers: {}
}),
{
status: 200,
headers: {
trace: '21'
},
body: 'Hello World!'
}
)
})
1 change: 1 addition & 0 deletions middleware/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {default as compose} from './compose.js'

0 comments on commit dfb8a82

Please sign in to comment.