{"id":13672753,"url":"https://github.com/mattreid1/baojs","last_synced_at":"2025-04-08T09:09:11.313Z","repository":{"id":44358922,"uuid":"512216208","full_name":"mattreid1/baojs","owner":"mattreid1","description":"⚡️ A fast, minimalist web framework for the Bun JavaScript runtime","archived":false,"fork":false,"pushed_at":"2024-03-29T07:34:47.000Z","size":331,"stargazers_count":957,"open_issues_count":17,"forks_count":31,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-04-01T07:45:20.831Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mattreid1.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-09T15:12:09.000Z","updated_at":"2025-03-18T18:02:34.000Z","dependencies_parsed_at":"2024-06-18T22:44:14.587Z","dependency_job_id":"0f586d94-8ce4-4df7-856c-2f2a381e9634","html_url":"https://github.com/mattreid1/baojs","commit_stats":{"total_commits":22,"total_committers":5,"mean_commits":4.4,"dds":"0.40909090909090906","last_synced_commit":"6598f3d3aad84dc3d3e66452c62767dad7f7b96d"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattreid1%2Fbaojs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattreid1%2Fbaojs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattreid1%2Fbaojs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattreid1%2Fbaojs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mattreid1","download_url":"https://codeload.github.com/mattreid1/baojs/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247809964,"owners_count":20999816,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T09:01:46.395Z","updated_at":"2025-04-08T09:09:11.291Z","avatar_url":"https://github.com/mattreid1.png","language":"TypeScript","funding_links":[],"categories":["Uncategorized","Packages","others","Extensions","TypeScript","Frameworks \u0026 Libraries"],"sub_categories":["Uncategorized","Web Framework","Frameworks"],"readme":"# 🥟 Bao.js\n\n[![](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)\n[![npm](https://img.shields.io/npm/v/baojs.svg)](https://www.npmjs.com/package/baojs)\n[![npm](https://img.shields.io/npm/l/baojs.svg)](https://spdx.org/licenses/MIT)\n[![npm](https://img.shields.io/npm/dt/baojs.svg)](\u003c[![npm](https://img.shields.io/npm/v/npm.svg)](https://www.npmjs.com/package/baojs)\u003e)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"media/bao175.png\" alt=\"Logo\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nA fast, minimalist web framework for the [Bun JavaScript runtime](https://bun.sh/).\n\n⚡️ **Bao.js** is **3.7x faster** than **Express.js** and has similar syntax for an easy transition.\n\n## Table of Contents\n\n- [Background](#background)\n- [Install](#install)\n- [Usage](#usage)\n  - [Examples](#examples)\n  - [Middleware](#middleware)\n- [Benchmarks](#benchmarks)\n- [Contribute](#contribute)\n- [License](#license)\n\n## Background\n\nBun was released as a fast, modern JavaScript runtime. One of the many improvements over Node.js was the 2.5x increase in HTTP request throughput when compared to the Node.js `http` module ([reference](https://github.com/oven-sh/bun/blob/7d1c9fa1a471d180c078a860c4885478f334bdf5/README.md#bunserve---fast-http-server)).\n\nBao.js uses Bun's built in `Bun.serve` module to serve routes and uses a radix tree for finding those routes resulting in exceptionally low latency response times. Bao is loosely syntactically modeled after [Express.js](https://github.com/expressjs/express) and [Koa.js](https://koajs.com/) with a handful of changes and upgrades to help with speed and improve the developer experience.\n\nBao works by creating a `Context` object (`ctx`) for each request and passing that through middleware and the destination route. This Context object also has various shortcut methods to make life easier such as by having standard response types (e.g. `ctx.sendJson({ \"hello\": \"world\" })`). When a route or middleware is finished, it should return the Context object to pass it along the chain until it is sent to the user.\n\nThe code is well documented and uses TypeScript, but more complete documentation will be added here in the future. It is not recommended to use this in production yet as both Bun and Bao.js are in beta.\n\n## Install\n\nAlthough this package is distributed via NPM, is only compatible with Bun (not Node.js) as it uses native Bun libraries.\n\nYou must first install [Bun](https://bun.sh/) and use it to run your server.\n\n🧑‍💻 To install Bao.js, in your project directory, run `bun add baojs`\n\n## Usage\n\nYou can import Bao by using\n\n```typescript\nimport Bao from \"baojs\";\n\nconst app = new Bao();\n```\n\nTo create a GET route, run\n\n```typescript\napp.get(\"/\", (ctx) =\u003e {\n  return ctx.sendText(\"OK\");\n});\n```\n\nThen to get Bao to listen for requests, run\n\n```typescript\napp.listen();\n```\n\nThis will start a web server on the default port 3000 listening on all interfaces (`0.0.0.0`). The port can be modified in the `listen()` options\n\n```typescript\napp.listen({ port: 8080 });\n```\n\n### Examples\n\n#### Hello World\n\nRun `bun index.ts`\n\n```typescript\n// index.ts\nimport Bao from \"baojs\";\n\nconst app = new Bao();\n\napp.get(\"/\", (ctx) =\u003e {\n  return ctx.sendText(\"Hello World!\");\n});\n\nconst server = app.listen();\n\nconsole.log(`Listening on ${server.hostname}:${server.port}`);\n```\n\n#### Read request body\n\nRun `bun index.ts`\n\n```typescript\n// index.ts\nimport Bao from \"baojs\";\n\nconst app = new Bao();\n\napp.post(\"/pretty\", async (ctx) =\u003e {\n  const json = await ctx.req.json();\n  return ctx.sendPrettyJson(json);\n});\n\napp.listen();\n```\n\n#### Named parameters\n\nRun `bun index.ts`\n\n```typescript\n// index.ts\nimport Bao from \"baojs\";\n\nconst app = new Bao();\n\napp.get(\"/user/:user\", (ctx) =\u003e {\n  const user = await getUser(ctx.params.user);\n  return ctx.sendJson(user);\n});\n\napp.get(\"/user/:user/:post/data\", (ctx) =\u003e {\n  const post = await getPost(ctx.params.post);\n  return ctx.sendJson({ post: post, byUser: user });\n});\n\napp.listen();\n```\n\n#### Wildcards\n\nWildcards are different to named parameters as wildcards must be at the end of paths as they will catch everything.\n\nThe following would be produced from the example below\n\n- `GET /posts/123` =\u003e `/123`\n- `GET /posts/123/abc` =\u003e `/123/abc`\n\nRun `bun index.ts`\n\n```typescript\n// index.ts\nimport Bao from \"baojs\";\n\nconst app = new Bao();\n\napp.get(\"/posts/*post\", (ctx) =\u003e {\n  return ctx.sendText(ctx.params.post);\n});\n\napp.listen();\n```\n\n#### Custom error handler\n\nRun `bun index.ts`\n\n```typescript\n// index.ts\nimport Bao from \"baojs\";\n\nconst app = new Bao();\n\napp.get(\"/\", (ctx) =\u003e {\n  return ctx.sendText(\"Hello World!\");\n});\n\n// A perpetually broken POST route\napp.post(\"/broken\", (ctx) =\u003e {\n  throw \"An intentional error has occurred in POST /broken\";\n  return ctx.sendText(\"I will never run...\");\n});\n\n// Custom error handler\napp.errorHandler = (error: Error) =\u003e {\n  logErrorToLoggingService(error);\n  return new Response(\"Oh no! An error has occurred...\");\n};\n\n// Custom 404 not found handler\napp.notFoundHandler = (ctx) =\u003e {\n  return new Response(\"Route not found...\");\n};\n\napp.listen();\n```\n\n### WebSockets\n\nBao.js can handle the routing of WebSockets too. Under the hood, it takes advantage of the native Bun WebSocket which itself uses [µWebSockets](https://github.com/uNetworking/uWebSockets), a compiled and highly optimized web server.\n\n```typescript\n// index.ts\nimport { v4 as uuidv4 } from \"uuid\";\nimport Bao from \"baojs\";\n\nconst app = new Bao();\n\n// Ping/pong\napp.ws(\"/ping\", {\n  message: (ws, msg) =\u003e {\n    ws.send(\"pong\");\n  },\n});\n\n// Simple chat room\napp.ws(\"/room/:roomId\", {\n  open: (ws) =\u003e {\n    const roomId = ws.data.ctx.params.roomId;\n    ws.data.uuid = uuidv4();\n    ws.publish(roomId, `New user \"${ws.data.uuid}\" joined \"${roomId}\"`);\n    ws.subscribe(roomId);\n  },\n  close: (ws) =\u003e {\n    const roomId = ws.data.ctx.params.roomId;\n    ws.publish(roomId, `User \"${ws.data.uuid}\" disconnected`);\n    ws.unsubscribe(roomId);\n  },\n  message: (ws, msg) =\u003e {\n    const roomId = ws.data.ctx.params.roomId;\n    const uuid = ws.data.uuid;\n    ws.publish(roomId, `${uuid}: ${msg}`);\n  },\n});\n\nconst server = app.listen();\nconsole.log(`Listening on ${server.hostname}:${server.port}`);\n```\n\n#### Authenticated WebSockets\n\n```typescript\n// index.ts\nimport Bao from \"baojs\";\n\nconst app = new Bao();\n\n// Ping/pong\napp.ws(\"/protected/ping\", {\n  upgrade: (ctx) =\u003e {\n    const secretToken = process.env.SECRET;\n\n    if (ctx.headers.get(\"authorization\") !== `Bearer ${secretToken}`) {\n      // forceSend() is required here to deny the upgrade\n      return ctx.sendText(\"Unauthorized\", { status: 403 }).forceSend();\n    }\n\n    // User is now authenticated\n\n    // Will propagate to all handlers once the upgrade is complete\n    ctx.extra.user = getUser(ctx);\n\n    return ctx;\n  },\n  open: (ws) =\u003e {\n    // Only for authorized users\n    const user = ws.data.ctx.extra.user;\n    console.log(`User \"${user}\" connected with the secret token`);\n  },\n  message: (ws, msg) =\u003e {\n    // Only for authorized users\n    ws.send(\"secret pong\");\n  },\n});\n\nconst server = app.listen();\n```\n\n### Middleware\n\nMiddleware is split into middleware that runs before the routes, and middleware that runs after them. This helps to contribute to the performance of Bao.js. Note that the middleware only runs for HTTP routes and not for WebSockets - however, it does run prior to the connection being upgraded to a WebSocket.\n\n```typescript\n// index.ts\nimport Bao from \"baojs\";\n\nconst app = new Bao();\n\n// Runs before the routes\napp.before((ctx) =\u003e {\n  const user = getUser(ctx.headers.get(\"Authorization\"));\n  if (user === null) return ctx.sendEmpty({ status: 403 }).forceSend();\n  ctx.extra[\"auth\"] = user;\n  return ctx;\n});\n\napp.get(\"/\", (ctx) =\u003e {\n  return ctx.sendText(`Hello ${ctx.extra.user.displayName}!`);\n});\n\n// Runs after the routes\napp.after((ctx) =\u003e {\n  ctx.res.headers.append(\"version\", \"1.2.3\");\n  return ctx;\n});\n\napp.listen();\n```\n\nThe `.forceSend()` method tells Bao to not pass the Context object to anything else but instead send it straight to the user. This is useful in cases like this where we don't want unauthenticated users to be able to access our routes and so we just reject their request before it can make it to the route handler.\n\n### Benchmarks\n\nBenchmarks were conducted using [`wrk`](https://github.com/wg/wrk) with the results shown below.\n\n#### Bao.js\n\n```shell\n$ wrk -t12 -c 500 -d10s http://localhost:3000/\nRunning 10s test @ http://localhost:3000/\n  12 threads and 500 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency    15.38ms    1.47ms  39.19ms   76.59%\n    Req/Sec     2.67k   195.60     2.90k    82.33%\n  318588 requests in 10.01s, 24.31MB read\n  Socket errors: connect 0, read 667, write 0, timeout 0\nRequests/sec:  31821.34\nTransfer/sec:      2.43MB\n```\n\n```typescript\nimport Bao from \"baojs\";\n\nconst app = new Bao();\n\napp.get(\"/\", (ctx) =\u003e {\n  return ctx.sendText(\"OK\");\n});\n\napp.listen();\n```\n\n#### Express.js\n\nBao.js can handle **3.7x more** requests per second, with an equal **3.7x reduction in latency** per request when compared to Express.js.\n\n```shell\n$ wrk -t12 -c 500 -d10s http://localhost:5000/\nRunning 10s test @ http://localhost:5000/\n  12 threads and 500 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency    56.34ms   13.42ms 246.38ms   90.62%\n    Req/Sec   729.26    124.31     0.88k    86.42%\n  87160 requests in 10.01s, 18.95MB read\n  Socket errors: connect 0, read 928, write 0, timeout 0\nRequests/sec:   8705.70\nTransfer/sec:      1.89MB\n```\n\n```javascript\nconst express = require(\"express\");\nconst app = express();\n\napp.get(\"/\", (req, res) =\u003e {\n  res.send(\"OK\");\n});\n\napp.listen(5000);\n```\n\n#### Koa.js\n\nBao.js can handle **1.2x more** requests per second, with an equal **1.2x reduction in latency** per request when compared to the modern Koa.js.\n\n```shell\n$ wrk -t12 -c 500 -d10s http://localhost:1234/\nRunning 10s test @ http://localhost:1234/\n  12 threads and 500 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency    18.12ms    2.47ms  65.03ms   91.12%\n    Req/Sec     2.26k   280.16     4.53k    90.46%\n  271623 requests in 10.11s, 42.74MB read\n  Socket errors: connect 0, read 649, write 0, timeout 0\nRequests/sec:  26877.94\nTransfer/sec:      4.23MB\n```\n\n```javascript\nconst Koa = require(\"koa\");\nconst app = new Koa();\n\napp.use((ctx) =\u003e {\n  ctx.body = \"OK\";\n});\n\napp.listen(1234);\n```\n\n#### Fastify\n\nBao.js is about equal to Fastify in both throughput and latency.\n\n```shell\n$ wrk -t12 -c 500 -d10s http://localhost:5000/\nRunning 10s test @ http://localhost:5000/\n  12 threads and 500 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency    15.32ms    1.90ms  60.53ms   78.74%\n    Req/Sec     2.68k   274.95     3.25k    72.08%\n  319946 requests in 10.01s, 50.65MB read\n  Socket errors: connect 0, read 681, write 0, timeout 0\nRequests/sec:  31974.36\nTransfer/sec:      5.06MB\n```\n\n```javascript\nconst fastify = require(\"fastify\");\nconst app = fastify({ logger: false });\n\napp.get(\"/\", () =\u003e \"OK\");\n\napp.listen({ port: 5000 });\n```\n\n## Contribute\n\nPRs are welcome! If you're looking for something to do, maybe take a look at the Issues?\n\nIf updating the README, please stick to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattreid1%2Fbaojs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmattreid1%2Fbaojs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattreid1%2Fbaojs/lists"}