README
passing-notes
Build an HTTP server out of composable blocks
Example
// server.mjs
export default function(request) {
console.log(request)
return {
status: 200,
headers: {
'Content-Type': 'text/plain'
},
body: 'Hello World!'
}
}
yarn pass-notes server.mjs
Handle HTTP requests with a function that takes request data and returns response data. Compose your functions with pre-built functions to quickly implement complex workflows.
Installation
Install passing-notes 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.
// 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:
yarn pass-notes server.mjs
Or from JavaScript:
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:
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.
Pre-Built Middleware
We've built and packaged some middleware that handle common use cases:
static
: Serves static files from the file systemui
: Serves application code to the browserrpc
: Simple communication between browser and server
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.
Per-Environment Configuration
A common pattern for implementing per-environment configuration is to store that configuration in a file that is modified per environment. This is useful for scenarios for which it's not convenient to directly set environment variables.
We support setting environment variables via a .env.yml
file:
FOO: string
BAR:
- JSON
- array
BAZ:
key1: JSON
key2: object
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:
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:
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
A CLI that takes an ES module that exports an HTTP request handler and uses it to start an HTTP server.
// server.mjs
export default function(request) {
console.log(request)
return {
status: 200,
headers: {
'Content-Type': 'text/plain'
},
body: 'Hello World!'
}
}
yarn pass-notes server.mjs
Request Handler
The ES module's default export must be a function that takes as argument an object with the following keys:
version
: The HTTP version used, either'1.1'
or'2.0'
method
: An HTTP request method in capital letters (e.g.GET
orPOST
)url
: The absolute URL or path to a resourceheaders
: An object mapping case-insensitive HTTP header names to valuesbody
: The HTTP request body as a string or buffer
And returns an object (or Promise
resolving to an object) with the following
keys:
status
: The HTTP response status code (e.g.200
)headers
body
push
: An optional array of requests that will be fed back into the request handler to compute responses and then pushed to the client. This is only supported over HTTP/2 (indicated byrequest.version
being'2.0'
).
Protocol Support
This HTTP server supports HTTP/1.1 and HTTP/2 as well as TLS.
TLS Configuration
A self-signed certificate is automatically generated for localhost
when
NODE_ENV
is not set to production
. Otherwise, a certificate can be provided
by exporting an object named tls
containing any of the options for
tls.createSecureContext
,
for example:
export const tls = {
cert: 'PEM format string',
key: 'PEM format string'
}
CERT
and KEY
can also be provided as environment variables.
Hot-Reloading
When NODE_ENV
is not set to production
, the provided ES module is
re-imported whenever it or its dependencies change. Note that node_modules
are
never re-imported.
Logging
By default, the method and URL for every request is logged to STDOUT.
In order to log additional events to STDOUT, a custom logger can be created and exported:
import {Logger} from 'passing-notes'
export const logger = new Logger()
This logger is expected to provide the following interface:
- It extends
EventEmitter
- It emits
log
events with two arguments:event
: An object containing:time
: A UNIX timestamplevel
: One ofTRACE
,DEBUG
,INFO
,WARN
,ERROR
, orFATAL
topic
: A string that categorizes the log eventmessage
: A description of the log eventduration
: An optional millisecond durationerror
: An optionalError
object to print
logLine
a formatted string to print to STDOUT
log(event)
: Computes a timestamp and emits alog
event.measure(event)
: Logs the start of a task. Returns a function that when called, computes the duration and logs the end of the task.