Fast, and friendly Bun web framework

Overview

🦊 KingWorld

Fast, and friendly Bun web framework.

⚡️ Faster than Express.js by 8.5x on M1 Max

Named after my favorite VTuber (Shirakami Fubuki) and composer (Sasakure.UK) song KINGWORLD/白上フブキ(Original)

KingWorld is web framework built for Bun. It focuses on speed, and developer friendliness.

Borrowing concepts from many popular Node.js web frameworks, KingWorld has a very familiar API designed to easily get started while hiding complex abstractions, and embracing simplicity.

Ecosystem

KingWorld can be heavily customized with the use of plugins.

Currently, you can take a look at these:

  • Static for serving static file/folders
  • Cookie for reading/setting cookie
  • Schema for validating request declaratively
  • CORS for handling CORs request

Quick Start

KingWorld is a web framework based on Bun.

bun add kingworld

Now create index.ts, and place the following:

import KingWorld from 'kingworld'

new KingWorld()
    .get("/", () => "🦊 Now foxing")
    .listen(3000)

And run the server:

bun index.ts

Then simply open http://localhost:3000 in your browser.

Congrats! You have just create a new web server in KingWorld 🎉 🎉

Routing

Common HTTP methods have a built-in methods for convenient usage:

app.get("/hi", () => "Hi")
    .post("/hi", () => "From Post")
    .put("/hi", () => "From Put")
    .on("M-SEARCH", async () => "Custom Method")
    .listen(3000)

// [GET] /hi => "Hi"
// [POST] /hi => "From Post"
// [PUT] /hi => "From Put"
// [M-SEARCH] /hi => "Custom Method"

To return JSON, simply return any serializable object:

app.get("/json", () => ({
    hi: 'KingWorld'
}))

// [GET] /json => {"hi": "KingWorld"}

All values returned from handler will be transformed into Response.

You can return Response if you want to declaratively control the response.

app
    .get("/number", () => 1)
    .get("/boolean", () => true)
    .get("/promise", () => new Promise((resovle) => resolve("Ok")))
    .get("/response", () => new Response("Hi", {
        status: 200
    }))

// [GET] /number => "1"
// [GET] /boolean => "true"
// [GET] /promise => "Ok"
// [GET] /response => "Hi"

Files are also transformed to response as well. Simply return Bun.file to serve static file.

app.get("/tako", () => Bun.file('./example/takodachi.png'))

To get path paramameters, prefix the path with a colon:

app.get("/id/:id", ({ params: { id } }) => id)

// [GET] /id/123 => 123

To ensure the type, simply pass a generic:

app.get<{
    params: {
        id: string
    }
}>("/id/:id", ({ params: { id } }) => id)

// [GET] /id/123 => 123

Wildcards are also acceptable:

app.get("/wildcard/*", () => "Hi")

// [GET] /wildcard/ok => "Hi"
// [GET] /wildcard/abc/def/ghi => "Hi"

For custom 404 page, use default:

app.get("/", () => "Hi")
    .default(() => new Response("Not stonk :(", {
        status: 404
    }))

// [GET] / => "Not stonk :("

You can group multiple route with a prefix with group:

app
    .get("/", () => "Hi")
    .group("/auth", app => {
        app
            .get("/", () => "Hi")
            .post("/sign-in", ({ body }) => body)
            .put("/sign-up", ({ body }) => body)
    })
    .listen(3000)

// [GET] /auth/sign-in => "Hi"
// [POST] /auth/sign-in => <body>
// [PUT] /auth/sign-up => <body>

Finally, you can decouple the route logic to a separate plugin.

import KingWorld, { type Plugin } from 'kingworld'

const hi: Plugin = (app) => app
    .get('/hi', () => 'Hi')

const app = new KingWorld()
    .use(hi)
    .get('/', () => 'KINGWORLD')
    .listen(3000)

// [GET] / => "KINGWORLD"
// [GET] /hi => "Hi"

Handler

Handler is a callback function that returns Response. Used in HTTP method handler.

new KingWorld()
    .get(
        '/', 
        // This is handler
        () => "KingWorld"
    )
    .listen(3000)

By default, handler will accepts two parameters: request and store.

// Simplified Handler
type Handler = (request: ParsedRequest, store: Instance['store']) => Response

const handler: Handler = (request: {
    request: Request
    query: ParsedUrlQuery
    params: Record<string, string>
    headers: Record<string, string>
    body: Promise<string | Object>
    responseHeaders: Headers
}, store: Record<any, unknown>)

Handler Request

Handler's request consists of

  • request [Request]
    • Native fetch Request
  • query [ParsedUrlQuery]
    • Parsed Query Parameters as Record<string, string>
    • Default: {}
    • Example:
      • path: /hi?name=fubuki&game=KingWorld
      • query: { "name": "fubuki", "game": "KingWorld" }
  • params [Record<string, string>]
    • Path paramters as object
    • Default: {}
    • Example:
      • Code: app.get("/id/:name/:game")
      • path: /id/kurokami/KingWorld
      • params: { "name": "kurokami", "game": "KingWorld" }
  • headers [Record<string, string>]
    • Function which returns request's headers
  • body [Promise<string | Object>]
    • Function which returns request's body
    • By default will return either string or Object
      • Will return Object if request's header contains Content-Type: application/json, and is deserializable
      • Otherwise, will return string
  • responseHeaders [Header]
    • Mutable object reference, will attached to response's header
    • For example, adding CORS to response as a plugin

Store

Store is a singleton store of the application.

Is recommended for local state, reference of database connection, and other things that need to be available to be used with handler.

Store value if of 2 types:

  • State: Assigned once at creation
  • Ref: Assign at every request
new KingWorld()
    .state('build', Math.random())
    .ref('random', () => Math.random())
    .get("/build", ({}, { build }) => build)
    .get("/random", ({}, { random }) => random)
    .listen(3000)

// [GET] /build => 0.5
// [GET] /build => 0.5 // Will have the same value as first request
// [GET] /date => 0.374
// [GET] /date => 0.785
// [GET] /date => 0.651

State will have any value assigned, eg. Function will be a function reference. However for ref, if a value is a function, it will be called once.

This is for convenient usage of complex logic assigning at the beginning of every request.

You can assign a function to ref by assigning another callback, however if you want to assign function, please use state instead because function should be static.

// ❌ Function is assigned on every request
new KingWorld()
    .ref('getRandom', () => () => Math.random())
    .get("/random", ({}, { getRandom }) => getRandom())

// ✅ Function is assigned once
new KingWorld()
    .state('getRandom', () => Math.random())
    .get("/random", ({}, { getRandom }) => getRandom())

Typed Store

KingWorld accepts generic to type a store globally.

new KingWorld<{
    store: {
        build: number
        random: number
    }
}>()
    .state('build', Math.random())
    .ref('random', () => Math.random())
    .get("/build", ({}, { build }) => build)
    .get("/random", ({}, { random }) => random)
    .listen(3000)

Lifecycle

KingWorld request's lifecycle can be illustrate as the following:

Request -> onRequest -> route -> transform -> preHandler -> Response

The callback that assigned to lifecycle is called hook.

Pre Handler

  • onRequest
    • Call on new request

Internal

  • router.find (route)
    • Find handler assigned to route

Post Handler

  • transform [Handler]
    • Called before validating request
    • Use to transform request's body, params, query before validation
  • preHandler [Handler]
    • Handle request before executing path handler
    • If value returned, will skip to Response process

Lifecycle can be assigned with app.<lifecycle name>():

For example, assigning transform to a request:

app
    // ? Transform params 'id' to number if available
    .transform(({ params }) => {
        if(params.id)
            params.id = +params.id
    })

Local Hook

There's 2 type of hook

  • Global Hook
    • Assign to every handler
  • Local Hook
    • Assigned by third parameters of Route Handler or app.<method>(path, handler, localHook)
    • Affected only scoped handler
app
    // ? Global Hook
    .transform(({ params }) => {
        if(params.id)
            params.id = +params.id + 1
    })
    .get(
        "/id/:id/:name", 
        ({ params: { id, name } }) => `${id} ${name}`,
        // ? Local hook
        {
            transform: ({ params }) => {
                if(params.name === "白上フブキ")
                    params.name = "Shirakami Fubuki"
            }
        }
    )
    .get("/new/:id", ({ params: { id, name } }) => `${id} ${name}`)
    .listen(3000)

// [GET] /id/2/kson => "3 kson"
// [GET] /id/1/白上フブキ => "2 Shirakami Fubuki"
// [GET] /new/1/白上フブキ => "2 白上フブキ"

You can have multiple local hooks as well by assigning it as array:

app
    .get(
        "/id/:id/:name", 
        ({ params: { id, name } }) => `${id} ${name}`,
        {
            transform: [
                ({ params }) => {
                    if(params.id)
                        params.id = +params.id + 1
                },
                ({ params }) => {
                    if(params.name === "白上フブキ")
                        params.name = "Shirakami Fubuki"
                }
            ]
        }
    )
    .listen(3000)

// [GET] /id/2/kson => "3 kson"
// [GET] /id/1/白上フブキ => "2 Shirakami Fubuki"
// [GET] /new/1/白上フブキ => "2 白上フブキ"

PreRequestHandler

Callback assign to lifecycle before routing.

As it's handle before routing, there's no params, query.

type PreRequestHandler = (request: Request, store: Store) => void

Lifecycle that assigned with PreRequestHandler:

  • onRequest

Handler (Event)

Callback assign to lifecycle after routing.

Accept same value as path handler, @see Handler

Lifecycle that assigned with Handler:

  • transform
  • preHandler

Transform

Use to modify request's body, params, query before validation.

app
    .get(
        "/gamer/:name", 
        ({ params: { name }, hi }) => hi(name),
        // ? Local hook
        {
            transform: ({ params }) => {
                if(params.name === "白上フブキ")
                    params.name = "Shirakami Fubuki"
                    
                params.hi = (name: string) => `Hi ${name}`
            }
        }
    )

// [GET] /gamer/白上フブキ => "Shirakami Fubuki"
// [GET] /gamer/Botan => "Botan"

As body is lazily execute as promise, simply use .then to modify body.

new KingWorld()
	.post<{
		body: {
			id: number
			username: string
		}
	}>(
		'/gamer',
		async ({ body }) => {
			const { username } = await body

			return `Hi ${username}`
		},
		{
			transform: (request) => {
				request.body = request.body.then((user) => {
					user.id = +user.id

					return user
				})
			}
		}
	)
	.listen(8080)

Schema Validation

Please use @kingworldjs/schema to handle typed-strict validation of incoming request.

Example

import KingWorld from 'kingworld'
import schema, { S } from '@kingworldjs/schema'

new KingWorld()
    .get('/id/:id', ({ request: { params: { id } } }) => id, {
        transform: (request, store) {
            request.params.id = +request.params.id
        },
        preHandler: schema({
            params: S.object().prop('id', S.number().minimum(1).maximum(100))
        })
    })
    .listen(3000)

// [GET] /id/2 => 2
// [GET] /id/500 => Invalid params
// [GET] /id/-3 => Invalid params

See @kingworldjs/schema for more detail about schema validation.

PreHandler

Handle request before executing path handler. If value is returned, the value will be the response instead and skip the path handler.

Schema validation is useful, but as it only validate the type sometime app require more complex logic than type validation.

For example: Checking value if value existed in database before executing the request.

import KingWorld, { S } from 'kingworld'

new KingWorld()
    .post<{
        body: {
            username: string
        }
    }>('/id/:id', ({ request: { body }) => {
            const { username } = await body

            return `Hi ${username}`
        }, {
        preHandler: async ({ body }) => {
            const { username } = await body
            const user = await database.find(username)

            if(user)
                return user.profile
            else
                return Response("User doesn't exists", {
                    status: 400
                })
        }
    })
    .listen(3000)

Plugin

Plugin is used to decouple logic into smaller function.

import KingWorld, { type Plugin } from 'kingworld'

const hi: Plugin = (app) => app
    .get('/hi', () => 'Hi')

const app = new KingWorld()
    .use(hi)
    .get('/', () => 'KINGWORLD')
    .listen(3000)

// [GET] / => "KINGWORLD"
// [GET] /hi => "Hi"

However, plugin can also be used for assigning new store, and hook making it very useful.

To register a plugin, simply add plugin into use.

use can accept 2 parameters:

  • plugin [Plugin]
  • config [Config?] (Optional)
const plugin: Plugin = (
    app, 
    // Config (2nd paramters of `use`)
    { prefix = '/fbk' } = {}
) => app
        .group(prefix, (app) => {
            app.get('/plugin', () => 'From Plugin')
        })

new KingWorld()
    .use(app, {
        prefix: '/fubuki'
    })

To develop plugin with type support, Plugin can accepts generic.

const plugin: Plugin<
    // ? Typed Config
    {
        prefix?: string
    },
    // ? Same as KingWorld<{}>(), will extends current instance
    {
        store: {
            fromPlugin: 'From Logger'
        }
        request: {
            log: () => void
        }
    }
> = (app, { prefix = '/fbk' } = {})  => 
    app
        .state('fromPlugin', 'From Logger')
        .transform(({ responseHeaders }) => {
            request.log = () => {
                console.log('From Logger')
            }

            responseHeaders.append('X-POWERED-BY', 'KINGWORLD')
        })
        .group(prefix, (app) => {
            app.get('/plugin', () => 'From Plugin')
        })

const app = new KingWorld<{
    Store: {
        build: number
        date: number
    }
}>()
    .use(plugin)
    .get('/', ({ log }) => {
        log()

        return 'KingWorld'
    })

// [GET] /fbk/plugin => "From Plugin"

Since Plugin have a type declaration, all request and store will be fully type and extended from plugin.

For example:

// Before plugin registration
new KingWorld<{
    Store: {
        build: number
        date: number
    }
}>()

// After plugin registration
new KingWorld<{
    Store: {
        build: number
        date: number
    } & {
        fromPlugin: 'From Logger'
    }
    Request: {
        log: () => void
    }
}>()

This will enforce type safety across codebase.

const app = new KingWorld<{
    Store: {
        build: number
        date: number
    }
}>()
    .use(plugin)
    .get('/', ({ log }) => {
        // `log` get type declaration reference from `plugin`
        log()

        return 'KingWorld'
    })

Local plugin custom type

Sometime, when you develop local plugin, type reference from main instance is need, but not available after separation.

const plugin: Plugin = (app)  => 
    app
        .get("/user/:id", ({ db, params: { id } }) => 
            // ❌ Type Error: db is not defined or smth like that
            db.find(id)
        )

const app = new KingWorld<{
    Store: {
        database: Database
    }
}>()
    .state('db', database)
    .use(plugin)

That's why plugin can accept the third generic for adding temporary local type but do not extend the main instance.

const plugin: Plugin<
    {},
    {},
    // Same as KingWorld<Instance>
    {
        store: {
            db: Database
        }
    }
> = (app)  => 
    app
        .get("/user/:id", ({ db, params: { id } }) => 
            // ✅ db is now typed
            db.find(id)
        )

const app = new KingWorld<{
    store: {
        database: Database
    }
}>()
    .state('db', database)
    .use(plugin)

KingWorld Instance

KingWorld can accepts named generic to type global instance.

For example, type-strict store.

const app = new KingWorld<{
    Store: {
        build: number
    }
}>()
    .state('build', 1)

KingWorld instance can accept generic of KingWorldInstance

export interface KingWorldInstance<
	Store extends Record<string, any> = {},
	Request extends Record<string, any> = {}
> {
	Request?: Request
	Store: Store
}

Test

KingWorld is designed to be serverless, only one simple handle is need to be assigned to serverless function.

This also be used to create simple test environment, by simply call handle function.

import { describe, expect, it } from "bun:test"

const req = (path: string) => new Request(path)

describe('Correctness', () => {
	it('[GET] /', async () => {
		const app = new KingWorld().get('/', () => 'Hi')
		const res = await app.handle(req('/'))

		expect(await res.text()).toBe('Hi')
	})
})

Caveat

Sometime KingWorld doesn't perform well in some situation or has some bug, which can be related to Bun.

Notable reference:

However, if you're sure that the bug is related to KingWorld, filing an issue is always welcome.

Optimization

For the current state of Bun, if you wants full speed of Bun, avoid using await in critical path.

As the state of Bun 0.1.3, KingWorld will be slowed down when using await which might occurs from the following:

  • Using await in handler
  • Using schema.body
  • Using request.body

The performance will be slowed down by around 1.75x - 3x vary on how powerful the machine is.

State of KingWorld

KingWorld is an experimental web framework based on bun.

A bleeding edge web framework focused on developer friendliness, and performance, but is not recommended for production.

As KingWorld is in 0.0.0-experimental.x, API is very unstable and will change in any point of time, at-least until 0.1.0 is release.

As bun is currently in early stage of development, some API might changed in the future which might also results KingWorld API to be changed to match the better speed and developer experience.

License

KingWorld is MIT License

Comments
  • Make leading slash optional.

    Make leading slash optional.

    This will make the leading slashes (mainly used for groups) completely optional. Meaning both http://localhost:8080/group/ and http://localhost:8080/group will both point to the same route.

    If you don't want the leading slash, a simple unwrapping of the getPath returning the leadingSlash function will do.

    All tests pass. image

    opened by Gibbu 2
  • Remove @medley/router

    Remove @medley/router

    This PR removes @medley/router and uses a router same as medley-router but with minor changes and typings.

    TODOS:

    • Write tests (I'm very new to jest and testing, So i'll probably need help for this)
    opened by gaurishhs 1
  • feat: hono regex router

    feat: hono regex router

    Dropping trek router fork to Hono Regex Router fork.

    This migration improves the router benchmark: Screen Shot 2565-08-13 at 10 12 30

    Benchmark: Running on MacBook Pro 14' M1 Max, 64GB RAM

    Trek Router fork:

    'use strict'
    
    const { title, now, print, operations } = require('../utils')
    const { default: Router } = require('@saltyaom/trek-router')
    
    const router = new Router()
    
    title('@saltyaom/trek-router benchmark')
    
    const routes = [
        { method: 'GET', url: 'http://localhost:8080/user' },
        { method: 'GET', url: 'http://localhost:8080/user/comments' },
        { method: 'GET', url: 'http://localhost:8080/user/avatar' },
        { method: 'GET', url: 'http://localhost:8080/user/lookup/username/:username' },
        { method: 'GET', url: 'http://localhost:8080/user/lookup/email/:address' },
        { method: 'GET', url: 'http://localhost:8080/event/:id' },
        { method: 'GET', url: 'http://localhost:8080/event/:id/comments' },
        { method: 'POST', url: 'http://localhost:8080/event/:id/comment' },
        { method: 'GET', url: 'http://localhost:8080/map/:location/events' },
        { method: 'GET', url: 'http://localhost:8080/status' },
        { method: 'GET', url: 'http://localhost:8080/very/deeply/nested/route/hello/there' },
        { method: 'GET', url: 'http://localhost:8080/static/*' }
    ]
    
    function noop() {}
    var i = 0
    var time = 0
    
    routes.forEach((route) => {
        router.add(route.method, route.url, noop)
    })
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.find('GET', '/user')
    }
    print('short static:', time)
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.find('GET', '/user/comments')
    }
    print('static with same radix:', time)
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.find('GET', '/user/lookup/username/john')
    }
    print('dynamic route:', time)
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.find('GET', '/event/abcd1234/comments')
    }
    print('mixed static dynamic:', time)
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.find('GET', '/very/deeply/nested/route/hello/there')
    }
    print('long static:', time)
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.find('GET', '/static/index.html')
    }
    print('wildcard:', time)
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.find('GET', 'http://localhost:8080/user')
        router.find('GET', 'http://localhost:8080/user/comments')
        router.find('GET', 'http://localhost:8080/user/lookup/username/john')
        router.find('GET', 'http://localhost:8080/event/abcd1234/comments')
        router.find('GET', 'http://localhost:8080/very/deeply/nested/route/hello/there')
        router.find('GET', 'http://localhost:8080/static/index.html')
    }
    print('all together:', time)
    

    Hono Router fork:

    import { RegExpRouter } from 'hono/dist/router/reg-exp-router'
    const { title, now, print, operations } = require('../utils')
    
    const router = new RegExpRouter()
    
    title('kingworld fork: hono/reg-exp-router')
    
    const routes = [
        { method: 'GET', url: 'http://localhost:8080/user' },
        { method: 'GET', url: 'http://localhost:8080/user/comments' },
        { method: 'GET', url: 'http://localhost:8080/user/avatar' },
        { method: 'GET', url: 'http://localhost:8080/user/lookup/username/:username' },
        { method: 'GET', url: 'http://localhost:8080/user/lookup/email/:address' },
        { method: 'GET', url: 'http://localhost:8080/event/:id' },
        { method: 'GET', url: 'http://localhost:8080/event/:id/comments' },
        { method: 'POST', url: 'http://localhost:8080/event/:id/comment' },
        { method: 'GET', url: 'http://localhost:8080/map/:location/events' },
        { method: 'GET', url: 'http://localhost:8080/status' },
        { method: 'GET', url: 'http://localhost:8080/very/deeply/nested/route/hello/there' },
        { method: 'GET', url: 'http://localhost:8080/static/*' }
    ]
    
    function noop() {}
    var i = 0
    var time = 0
    
    routes.forEach((route) => {
        router.add(route.method, route.url, noop)
    })
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.match('GET', '/user')
    }
    print('short static:', time)
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.match('GET', '/user/comments')
    }
    print('static with same radix:', time)
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.match('GET', '/user/lookup/username/john')
    }
    print('dynamic route:', time)
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.match('GET', '/event/abcd1234/comments')
    }
    print('mixed static dynamic:', time)
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.match('GET', '/very/deeply/nested/route/hello/there')
    }
    print('long static:', time)
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.match('GET', '/static/index.html')
    }
    print('wildcard:', time)
    
    time = now()
    for (i = 0; i < operations; i++) {
        router.match('GET', 'http://localhost:8080/user')
        router.match('GET', 'http://localhost:8080/user/comments')
        router.match('GET', 'http://localhost:8080/user/lookup/username/john')
        router.match('GET', 'http://localhost:8080/event/abcd1234/comments')
        router.match('GET', 'http://localhost:8080/very/deeply/nested/route/hello/there')
        router.match('GET', 'http://localhost:8080/static/index.html')
    }
    print('all together:', time)
    

    Utils:

    'use strict'
    
    const chalk = require('chalk')
    
    const operations = 1000000
    
    let hrtime
    
    // hrttime polyfill
    ;(function () {
        const nowOffset = Date.now()
        const now = () => Date.now() - nowOffset
        hrtime =
            process.hrtime ||
            ((previousTimestamp) => {
                const baseNow = Math.floor((Date.now() - now()) * 1e-3)
                const clocktime = now() * 1e-3
                let seconds = Math.floor(clocktime) + baseNow
                let nanoseconds = Math.floor((clocktime % 1) * 1e9)
    
                if (previousTimestamp) {
                    seconds = seconds - previousTimestamp[0]
                    nanoseconds = nanoseconds - previousTimestamp[1]
                    if (nanoseconds < 0) {
                        seconds--
                        nanoseconds += 1e9
                    }
                }
                return [seconds, nanoseconds]
            })
    })()
    
    function now() {
        var ts = hrtime()
    
        return ts[0] * 1e3 + ts[1] / 1e6
    }
    
    function getOpsSec(ms) {
        return Number(((operations * 1000) / ms).toFixed()).toLocaleString()
    }
    
    function print(name, time) {
        console.log(chalk.yellow(name), getOpsSec(now() - time), 'ops/sec')
    }
    
    function title(name) {
        console.log(
            chalk.green(`
    ${'='.repeat(name.length + 2)}
     ${name}
    ${'='.repeat(name.length + 2)}`)
        )
    }
    
    function Queue() {
        this.q = []
        this.running = false
    }
    
    Queue.prototype.add = function add(job) {
        this.q.push(job)
        if (!this.running) this.run()
    }
    
    Queue.prototype.run = function run() {
        this.running = true
        const job = this.q.shift()
        job(() => {
            if (this.q.length) {
                this.run()
            } else {
                this.running = false
            }
        })
    }
    
    module.exports = { now, getOpsSec, print, title, Queue, operations }
    
    opened by SaltyAom 1
  • Create a CI/CD documentation website

    Create a CI/CD documentation website

    I'm working on another bun project and I found that you can get more contributors with a website. I built a really simple website and documentation generation tool if you want to add it to your project. Its under the MIT License- I'm just sending it around the bun community so it gains some more traction. If you want to check it out, it's right here. It's all integrated with Github Actions so all you have to do is add the code to GitHub actions and then you're are all set. If you don't want to, just let me know and close this issue- good luck with your project!

    Also, just a tip, add a bunch of issues you need help with and label them "good first issue" because then people will see your project from places like goodfirstissues.com.

    opened by William-McGonagle 1
  • Revert

    Revert "feat: hono regex router"

    Reverts SaltyAom/kingworld#6

    I benchmark the wrong version of Trek Router. For some reason, KingWorld is slower when using Hono Regex Router when bruteforce.

    Screen Shot 2565-08-13 at 11 09 21
    opened by SaltyAom 0
  • errors always log

    errors always log

    import { KingWorld } from 'kingworld';
    
    new KingWorld()
        .get('/', () => 'Hi')
        .get('/error', () => {
            throw new Error('test')
        })
        .onError(error => {
            console.log(error.name === 'test');
        })
        .listen(8080)
    

    If you start the app and visit http://localhost:8080/error should only see "false" in the logs, instead I see this.

    ➜  bun-test git:(main) ✗ bun run ./src/index.ts
    [0.09ms] ".env"
    false
    1 | import { KingWorld } from 'kingworld';
    2 | 
    3 | new KingWorld()
    4 |     .get('/', () => 'Hi')
    5 |     .get('/error', () => {
    6 |         throw new Error('test')
                      ^
    error: test
          at /Users/xo/code/bun-test/src/index.ts:6:14
          at /Users/xo/code/bun-test/node_modules/kingworld/build/es/index.js:285:24
          at handle (/Users/xo/code/bun-test/node_modules/kingworld/build/es/index.js:220:17)
    
    good first issue 
    opened by OmgImAlexis 2
  • statusText doesn't work

    statusText doesn't work

    Currently this returns 200 HM as there seems to be a bug in bun. Tracking in https://discord.com/channels/876711213126520882/1044751397993840770/1044751397993840770

    return new Response(JSON.stringify({
        field: 'value'
    }), {
        status: 200,
        statusText: 'test'
    });
    
    opened by OmgImAlexis 0
  • Context doesn't type null

    Context doesn't type null

    import KingWorld from 'kingworld';
    
    type Data = {
        field: number;
    }
    
    const app = new KingWorld()
        .state<'result', Data | null>('result', null)
        .get('/', context => context.store.result.field)
        .listen(3000);
    	 
    console.log(`🦊 KINGWORLD is running at :${app.server.port}`);
    

    Context is typed as the following.

    (parameter) context: Context<TypedSchemaToRoute<MergeSchema<{}, {}>>, {
        result: Data;
    }>
    

    result should be typed as Data | null.

    Currently if I run this I get the following error when browsing to http://localhost:3000.

    null is not an object (evaluating 'context.store.result.field')
    
    good first issue 
    opened by OmgImAlexis 2
  • Allow use of dynamic ports?

    Allow use of dynamic ports?

    I was hoping something like this could be added?

    import KingWorld from 'kingworld'
    
    const server = new KingWorld()
    	.get('/', () => 'Hello KingWorld')
    	.listen(0)
    	 
    console.log('🦊 KINGWORLD is running at :%s', server.port)
    

    This would also help in this case.

    import KingWorld from 'kingworld'
    
    const server = new KingWorld()
    	.get('/', () => 'Hello KingWorld')
    	.listen(process.env.PORT ?? 0)
    
    console.log('🦊 KINGWORLD is running at :%s', server.port)
    
    opened by OmgImAlexis 3
Releases(0.0.0-experimental.51)
  • 0.0.0-experimental.51(Nov 22, 2022)

    0.0.0-experimental.51 - 22 Nov 2022

    [Just Right Slow] introduce breaking major changes of KingWorld, specific on a plugin system.

    Previously, we define plugin by accepting 2 parameters, KingWorld and Config like this:

    const plugin = (app: KingWorld, config) => app
    
    new KingWorld().use(plugin, {
        // Provide some config here
    })
    

    However, this has flaw by the design because:

    • No support for async plugin
    • No generic for type inference
    • Not possible to accept 3...n parameters (if need)
    • Hard/heavy work to get type inference

    To fix all of the problem above, KingWorld now accept only one parameter.

    A callback which return KingWorld Instance, but accept anything before that.

    const plugin = (config) => (app: KingWorld) => app
    
    new KingWorld().use(plugin({
        // provide some config here
    }))
    

    This is a workaround just like the way to register async plugin before exp.51, we accept any parameters in a function which return callback of a KingWorld instance.

    This open a new possibility, plugin can now be async, generic type is now possible.

    More over that, decorate can now accept any parameters as it doesn't really affect any performance or any real restriction.

    Which means that something like this is now possible.

    const a = <Name extends string = string>(name: Name) => (app: KingWorld) => app.decorate(name, {
        hi: () => 'hi'
    })
    
    new KingWorld()
        .use(a('customName'))
        // Retrieve generic from plugin, not possible before exp.51
        .get({ customName } => customName.hi())
    

    This lead to even more safe with type safety, as you can now use any generic as you would like.

    The first plugin to leverage this feature is jwt which can introduce jwt function with custom namespace which is type safe.

    Change:

    • new decorators property for assigning fast Context
    Source code(tar.gz)
    Source code(zip)
  • 0.0.0-experimental.39(Nov 8, 2022)

    [39みゅーじっく!] is the 100th star release for KingWorld 🎉

    This release doesn't have a big change for the code but has a big change for the community.

    Nested Schema

    .39 introduce better support for the nested schema. Instead of inheriting the global schema first, KingWorld now handles the local schema first.

    If a duplicated schema is found, KingWorld now infers type correctly instead of having infinite cycling.

    Community Plugin: Controllers

    Now we have another community plugin, Controllers is a plugin for defining decorator and controller-based routing.

    import { Controller, Get, kwControllers } from 'kingworld-controllers'; 
    
    // /users prefix
    @Controller('/users/')
    class UsersController {
      @Get()
      index() {
        return 'Hello World'
      }
    }
    
    const app = new KingWorld()
    
    app.use(kwControllers, {
      controllers: [UsersController],
    })
    
    app.listen(3000);
    

    GraphQL support with Yoga

    Screenshot 2565-11-08 at 19 32 50

    With @kingworldjs/graphql-yoga, you can now use GraphQL Yoga with KingWorld.

    Change Log

    Breaking Change:

    • method is changed to route

    Improvement:

    • LocalHook now prefers the nearest type instead of the merge
    • Merge the nearest schema first
    • add contentType as a second parameter for BodyParser

    Bug fix:

    • Correct type for after handle
    • Fix infinite cycling infer type for Handler
    Source code(tar.gz)
    Source code(zip)
  • 0.0.0-experimental.37(Nov 5, 2022)

    [Sage] is one of the major experimental releases and breaking changes of KingWorld.

    The major improvement of Sage is that it provides almost (if not) full support for TypeScript and type inference.

    Type Inference

    KingWorld has a complex type of system. It's built with the DRY principle in mind, to reduce the developer's workload.

    That's why KingWorld tries to type everything at its best, inferring type from your code into TypeScript's type.

    For example, writing schema with nested guard is instructed with type and validation. This ensures that your type will always be valid no matter what, and inferring type to your IDE automatically. FgqOZUYVUAAVv6a

    You can even type response to make your that you didn't leak any important data by forgetting to update the response when you're doing a migration.

    Validator

    KingWorld's validator now replaced zod, and ajv with @sinclair/typebox.

    With the new validator, validation is now faster than the previous version by 188x if you're using zod, and 4.1x if you're using ajv adapter.

    With Edge Computing in mind, refactoring to new validate dropped the unused packages and reduced size by 181.2KB. To give you an idea, KingWorld without a validator is around 10KB (non-gzipped).

    Memory usage is also reduced by almost half by changing the validator.

    According to M1 Max running example/simple.ts, running exp.36 uses 24MB of memory while exp.37 use 12MB of memory

    This greatly improves the performance of KingWorld in a long run.

    Changelog

    Breaking Change:

    • Replace zod, zod-to-json-schema, ajv, with @sinclair/typebox

    Improvement:

    • use now accept any non KingWorld<{}, any>
    • use now combine typed between current instance and plugin
    • use now auto infer type if function is inline
    • LocalHook can now infer params type from path string

    Change:

    • TypedSchema is now replaced with Instance['schema']
    Source code(tar.gz)
    Source code(zip)
  • 0.0.0-experimental.29(Nov 2, 2022)

    Regulus

    This version introduces rework for internal architecture. Refine, and unify the structure of how KingWorld works internally.

    Although many refactoring might require, I can assure you that this is for the greater good, as the API refinement lay down a solid structure for the future of KingWorld.

    Thanks to API refinement, this version also introduced a lot of new interesting features, and many APIs simplified.

    Notable improvements and new features:

    • Define Schema, auto-infer type, and validation
    • Simplifying Handler's generic
    • Unifying Life Cycle into one property
    • Custom Error handler, and body-parser
    • Before start/stop and clean up effect

    New Feature:

    • add schema to the local hook for strict-type validation and type inference.

      • Example:
      import { z } from 'zod'
      
      app.post("/id/:id", ({ body }) => body, {
          schema: {
              body: z.object({
                  username: z.string(),
                  password: z.string()
              }),
              params: {
                  id: z.string()
              }
          }
      })
      
    • add onError for handling custom error

    • add onStart, onStop for handling server state

    • add stop for stopping the server

    Breaking Change:

    • Remove TypedRoute generic on httpMethod, replaced with schema.

      • To migrate:
      // From
      app.post<{
          body: {
              username: string
              password: string
          },
          params: {
              id: string
          }
      }>('/id/:id', ({ body }) => body)
      
      // To
      import { z } from 'zod'
      
      app.post("/id/:id", ({ body }) => body, {
          schema: {
              body: z.object({
                  username: z.string(),
                  password: z.string()
              }),
              params: {
                  id: z.string()
              }
          }
      })
      
    • Method Hook is now replaced with LocalHook

    • Rename preHandler to beforeHandle

    • Rename bodyParser to onParse, on('parse')

    • Rename .when(event) to on(event)

    • Rename .on(HttpMethod) to .method(HttpMethod)

    • Replace HookEvent for LifeCycle

    • Move bodyParser, errorHandler to event: LifeCycleStore

    • Remove ability for local hook to accept RegisterHook[]

    • Remove onRequest on local hook, as it doesn't execute

    Bug fix:

    • JSON body parsed as string
    Source code(tar.gz)
    Source code(zip)
  • 0.0.0-experimental.28(Oct 30, 2022)

    0.0.0-experimental.28 - 30 Oct 2022

    Happy Halloween.

    This version named [GHOST FOOD] is one of the big improvements for KingWorld, I have been working on lately. It has a lot of feature change for better performance, and introduce lots of deprecation. Be sure to follow the migration section in Breaking Change.

    GHOST FOOD introduce a lot of breaking change, laying down a better foundation for KingWorld in the future. However, the API is still marked as unstable and some might change in the future before moving out of the experiment version.

    GHOST FOOD has better performance in every aspect, with an even better type system for KingWorld.

    Like auto infer type for state, inheriting type from plugins, and auto typing path parameters.

    And thanking you all for following this experimental project of mine, happy hacking until next time.


    New Feature:

    • Auto infer type from plugin after merging with use
    • decorate to extends Context method
    • add addParser, for custom handler for parsing body

    Breaking Change:

    • Moved store into context.store

      • To migrate:
      // From
      app.get(({}, store) => store.a)
      
      // To
      app.get(({ store }) => store.a)
      
    • ref, and refFn is now removed

    • Remove Plugin type, simplified Plugin type declaration

      • To migrate:
      // From
      import type { Plugin } from 'kingworld'
      const a: Plugin = (app) => app
      
      // To
      import type KingWorld from 'kingworld'
      const a = (app: KingWorld) => app
      
    • Migrate Header to Record<string, unknown>

      • To migrate:
      app.get("/", ({ responseHeader }) => {
          // From
          responseHeader.append('X-Powered-By', 'KingWorld')
      
          // To
          responseHeader['X-Powered-By', 'KingWorld']
      
          return "KingWorld"
      })
      

    Change:

    • Store is now globally mutable

    Improvement:

    • Faster header initialization
    • Faster hook initialization
    Source code(tar.gz)
    Source code(zip)
Bun-Bakery is a web framework for Bun. It uses a file based router in style like svelte-kit. No need to define routes during runtime.

Bun Bakery Bun-Bakery is a web framework for Bun. It uses a file based router in style like svelte-kit. No need to define routes during runtime. Quick

Dennis Dudek 44 Dec 6, 2022
⚡️ A fast, minimalist web framework for the Bun JavaScript runtime

?? Bao.js A fast, minimalist web framework for the Bun JavaScript runtime. ⚡️ Bao.js is 3.7x faster than Express.js and has similar syntax for an easy

Matt Reid 746 Dec 26, 2022
A zero-dependency, strongly-typed web framework for Bun, Node and Cloudflare workers

nbit A simple, declarative, type-safe way to build web services and REST APIs for Bun, Node and Cloudflare Workers. Examples See some quick examples b

Simon Sturmer 16 Sep 16, 2022
Tiny and expressive web framework for Bun.js

Bagel Bagel is a tiny and expressive web framework for Bun.js for building web APIs. Inspired by Express.js and Koa.js. Here we treat Typescript as fi

KaKeng Loh 34 Nov 25, 2022
A next-gen web framework made on top of bun

Eviate JS (WIP) Next-generation web framework to build powerful apps Features Simple: No more req or res. It's all ctx (context) and plain objects! Fa

eviatejs 17 Oct 30, 2022
Fast Differentiable Tensor Library in JavaScript and TypeScript with Bun + Flashlight

A fast differentiable tensor library for research in TypeScript and JavaScript. Built with bun + flashlight. ⚠️ This is experimental software! ⚠️ Usag

Meta Research 917 Jan 7, 2023
⚡️A minimalistic and sweet router for blazing fast bun

Melonpan is a simple and minimalistic web-router designed to work with Bun, keeping performance in mind. ?? Why Melonpan? no/minimal learning curve De

Hemanth Krishna 66 Jan 6, 2023
🦆 lightning fast duckdb bindings for bun runtime

@evan/duckdb lightning fast duckdb bindings for bun runtime Install bun add @evan/duckdb Features ?? batteries included ?? jit optimized bindings ?? 4

evan 29 Oct 20, 2022
A minimal routing library designed to sit on top of Bun's fast HTTP server.

siopao A minimal routing library designed to sit on top of Bun's fast HTTP server. Based on Radix Tree. Sio=Hot Pao=Bun Installation bun add siopao Us

Robert Soriano 69 Nov 8, 2022
A blazingly fast Bun.js filesystem router, with an unpleasantly smooth experience!

Oily A blazingly fast Bun.js filesystem router, with an unpleasantly smooth experience! Installation · Usage · Examples · Discord Installation Once yo

Aries 22 Dec 19, 2022
For web frameworks on Node, on Deno, and on Bun.

Web Framework Bench For web frameworks on Node, on Deno, and on Bun. Fast is not everything, but fast is everything. Motivation There are some benchma

Yusuke Wada 8 Sep 7, 2022
📦 SVGs, fast and developer friendly in Angular

View settings all icons fixed size e.g. 30px button to align all icons distributes button to align all icons onscreen button to align all icons offscr

Push Based 18 Nov 28, 2022
🚀 A boilerplate with generic configurations to a Nextjs project with bun, vitest, cicd and etc

?? Next.JS Template with Linter ?? Tools: NextJS Typescript ESLint (Code Pattern) Prettier (Formatter) Husky (Pre-commit) Vitest (Unit/Integration Tes

Rodrigo Victor 8 Dec 18, 2022
Serve static files using Bun.serve or Bao.js

serve-static-bun Serve static files using Bun.serve or Bao.js. Currently in beta. Aiming for similar features as expressjs/serve-static. Install This

Jakob Bouchard 10 Jan 1, 2023
zx inspired shell for Bun/Node.

?? bnx zx inspired shell for Bun/Node. Install bun add bnx # npm install bnx Usage import { $ } from 'bnx' const list = $`ls -l` const files = list.

Robert Soriano 50 Oct 16, 2022
Wrap a function with bun-livereload to automatically reload any imports inside the function the next time it is called

bun-livereload Wrap a function with bun-livereload to automatically reload any imports inside the function the next time it is called. import liveRelo

Jarred Sumner 19 Dec 19, 2022
TypeScript type definitions for Bun's JavaScript runtime APIs

Bun TypeScript type definitions These are the type definitions for Bun's JavaScript runtime APIs. Installation Install the bun-types npm package: # ya

Oven 73 Dec 16, 2022
An express-like API for bun server

?? bunrest What is bunrest ?? bunrest is an ExpressJs-like API for bun http server. Features ⚡ BLAZING FAST. Bun is super fast... 0️⃣ dependencies, wo

Rui. 113 Jan 2, 2023
Detects which package manager (bun, pnpm, yarn, npm) is used.

@skarab/detect-package-manager Detects which package manager (bun, pnpm, yarn, npm) is used based on the current working directory. Features Support p

null 5 Sep 3, 2022