Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/sachinraja/uttp
write your request handlers once, run anywhere
https://github.com/sachinraja/uttp
express fastify fetch h3 koa node
Last synced: 3 days ago
JSON representation
write your request handlers once, run anywhere
- Host: GitHub
- URL: https://github.com/sachinraja/uttp
- Owner: sachinraja
- License: mit
- Created: 2022-05-25T00:05:40.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-10-09T09:37:35.000Z (2 months ago)
- Last Synced: 2024-12-06T14:09:07.434Z (16 days ago)
- Topics: express, fastify, fetch, h3, koa, node
- Language: TypeScript
- Homepage:
- Size: 229 KB
- Stars: 36
- Watchers: 1
- Forks: 1
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# uttp
write your request handlers once, run anywhere
currently supports:
- [Node (vanilla HTTP)](https://nodejs.org/api/http.html)
- [Express](https://expressjs.com/)
- [Fastify](https://www.fastify.io/)
- [Fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) (Cloudflare Workers, Deno, SvelteKit, Astro, Remix, etc.)
- [h3](https://github.com/unjs/h3) (Nuxt)
- [Koa](https://koajs.com/)
- [AWS Lambda](https://aws.amazon.com/lambda/)## Install
```sh
npm install uttp
```## Usage
First, define your universal request handler:
```ts
// handler.ts
import { defineHandler } from 'uttp'export const handler = defineHandler(() => {
// return an object that will be used by each adapter
return {
// called on each request
handleRequest() {
// return a response object
// that will be sent by the server framework
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:
```ts
// adapters/node.ts
import { getNodeAdapter } from 'uttp/adapters/node'
import { handler } from '../handler'export const nodeHandler = getNodeAdapter(handler)
```Users would use it like this:
```ts
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:
```ts
// adapters/fastify.ts
import { getFastifyAdapter } from 'uttp/adapters/fastify'
import { handler } from '../handler'export const getFastifyPlugin = getFastifyAdapter(handler)
```Users would use it like this:
```ts
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:
```ts
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:
```ts
import { defineHandler } from 'uttp'export const handler = defineHandler((helpers) => {
return {
async handleRequest(req) {
// each adapter will pass helpers
// that conform to function signatures
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:
```ts
import { defineHandler } from 'uttp'interface HandlerOptions {
parse(text: string): any | Promise
maxBodySize?: number
}export const handler = defineHandler(
// specify options type here
// can specify as many arguments as you want after `helpers`
// which the user will need to pass
(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:
```ts
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:
```ts
import { defineHandler } from 'uttp'export const handler = defineHandler(() => {
return {
handleRequest() {
return { status: 200, body: 'Hello world!' }
},
adapterOptions: {
// limit body size
maxBodySize: 1000,
},
}
})
```## Starter Templates
See starter templates for how to setup a package that uses uttp.
- [uttp-starter](examples/starter)
## 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.
```ts
import {
runNode,
// runExpress,
// runFastify,
// runH3,
// runKoa,
} from 'uttp/utils/runners'
// your universal handler
import { handler } from './handler.js'runNode(
handler,
// handler options as an array
[{ token: 'secret' }],
// server-related options
{ port: 3000 },
)
```