uttp
write your request handlers once, run anywhere
currently supports:
Install
npm install uttp
Usage
First, define your universal request handler:
import { defineHandler } from 'uttp'
export const handler = defineHandler(() => {
return {
handleRequest() {
return {
status: 200,
body: 'Hello world!',
headers: { 'Content-Type': 'text/html' },
}
},
adapterOptions: {},
}
})
For all server frameworks uttp supports this will show Hello world!
as HTML.
Then you can use adapters to get middleware/plugins/handlers for the server frameworks.
For Node:
import { getNodeAdapter } from 'uttp/adapters/node'
import { handler } from '../handler'
export const nodeHandler = getNodeAdapter(handler)
Users would use it like this:
import { nodeHandler } from 'my-lib/adapters/node'
const server = createServer(await nodeHandler())
server.listen(3000)
This process is the same for other server frameworks.
For Fastify:
import { getFastifyAdapter } from 'uttp/adapters/fastify'
import { handler } from '../handler'
export const getFastifyPlugin = getFastifyAdapter(handler)
Users would use it like this:
import { getFastifyPlugin } from 'my-lib/adapters/fastify'
const server = fastify()
server.register(await getFastifyPlugin())
server.listen(3000)
Note these are placed in different entry points / files because uttp/adapters/*
imports directly from the server frameworks. You cannot export multiple handlers from the same entry point because users would be forced to install server frameworks that they are not using.
Request
A universal request object is passed to handleRequest
containing some common properties coerced from the individual frameworks:
import { defineHandler } from 'uttp'
export const handler = defineHandler(() => {
return {
handleRequest(req) {
if (req.method !== 'GET') {
return { status: 400, body: 'method must be get' }
}
return {
status: 200,
body: 'Hello world!',
headers: { 'Content-Type': 'text/html' },
}
},
adapterOptions: {},
}
})
Helpers
Request handlers are passed a set of universal functions that vary in implementation across frameworks but retain the same signature:
import { defineHandler } from 'uttp'
export const handler = defineHandler((helpers) => {
return {
async handleRequest(req) {
const body = await helpers.parseBodyAsString(req.rawRequest)
if (!body) {
return { status: 400, body: 'must pass body' }
}
const json = JSON.parse(body)
json.
return {
status: 200,
body: 'Hello world!',
headers: { 'Content-Type': 'text/html' },
}
},
adapterOptions: {},
}
})
If you need a helper that is not currently available, please create an issue.
User Options
Your request handler can take in options from users of your handler:
import { defineHandler } from 'uttp'
interface HandlerOptions {
parse(text: string): any | Promise<any>
maxBodySize?: number
}
export const handler = defineHandler(
(helpers, options: HandlerOptions) => {
return {
async handleRequest(req) {
const body = await helpers.parseBodyAsString(req.rawRequest)
if (!body) return { status: 400, body: 'must have body' }
const parsedBody = await options.parse(body)
return { status: 200 }
},
adapterOptions: {
maxBodySize: options.maxBodySize,
},
}
},
)
Users will pass options like this:
import { nodeHandler } from 'my-lib/adapters/node'
const server = createServer(await nodeHandler({ parse: JSON.parse }))
server.listen(3000)
Adapter Options
You must return an adapterOptions
object. These options may be derived from user options. Here is an example with a description of what each option does:
import { defineHandler } from 'uttp'
export const handler = defineHandler(() => {
return {
handleRequest() {
return { status: 200, body: 'Hello world!' }
},
adapterOptions: {
maxBodySize: 1000,
},
}
})
Starter Templates
See starter templates for how to setup a package that uses uttp.
Utilities
uttp comes with some utils to help you build and test your handler.
Runners
Runners are an easy way to get a server up for a framework by providing your handler. Only some frameworks are supported.
import {
runNode,
} from 'uttp/utils/runners'
import { handler } from './handler.js'
runNode(
handler,
[{ token: 'secret' }],
{ port: 3000 },
)