sstur / nbit

A zero-dependency, strongly-typed web framework for Bun, Node and Cloudflare workers
65 stars 4 forks source link

Is there a better way to type a custom request handler? #4

Closed ImLunaHey closed 1 year ago

ImLunaHey commented 1 year ago

There's gotta be a better way for users to type a custom handler than what I ended up with. 😅

If you haven't guessed Handler is the type I need help with.

import { createApplication } from '@nbit/bun';

const { defineRoutes, attachRoutes } = createApplication();

type Handler = Parameters<Parameters<Parameters<Parameters<typeof defineRoutes>[0]>[0]['post']>[1]>[0] |
    Parameters<Parameters<Parameters<Parameters<typeof defineRoutes>[0]>[0]['get']>[1]>[0] |
    Parameters<Parameters<Parameters<Parameters<typeof defineRoutes>[0]>[0]['put']>[1]>[0] |
    Parameters<Parameters<Parameters<Parameters<typeof defineRoutes>[0]>[0]['delete']>[1]>[0];

const authMiddleware = (request: Handler) => {
    if (request.headers.get('x-api-key') !== '123') throw new Error('Invalid API key');
};

const middlewareRoutes = defineRoutes(app => [
    app.post('/*', authMiddleware),
    app.get('/*', authMiddleware),
    app.put('/*', authMiddleware),
    app.delete('/*', authMiddleware),
]);

const appRoutes = defineRoutes(app => [
    app.get('/', () => {
        return { hello: 'world' };
    }),
]);

Bun.serve({
    port: 3000,
    fetch: attachRoutes(middlewareRoutes, appRoutes),
});
sstur commented 1 year ago

OK, you're right, that type looks like a nightmare and I should consider exporting a reasonable type helper, or some other way to define reusable route handlers like that.

On a side note, related to your example of implementing middleware, I think I need to come up with be a better way to officially support middleware.

We do have a concept of context (inspired by GraphQL servers). It's not exactly a 1:1 replacement for middleware like what you have, but it could be helpful for auth-related things. It would look something like this:

import { createApplication } from '@nbit/bun';

const { defineRoutes, attachRoutes } = createApplication({
  getContext: (request) => ({
    auth: async () => {
      if (request.headers.get('x-api-key') !== '123') {
        throw new Error('Invalid API key');
      }
      const user = await db.getUser(authKey);
      return user; // or session or something useful
    },
  }),
});

const appRoutes = defineRoutes((app) => [
  app.get('/', async (request) => {
    const user = await request.auth(); // Will throw if auth fails
    return { 'you are logged in as': user };
  }),
]);

Bun.serve({
  port: 3000,
  fetch: attachRoutes(appRoutes),
});
sstur commented 1 year ago

This is now fixed in v0.9. There is now a way to add Express-style middleware in a much simpler way. So for your example it would look like:

import { createApplication } from '@nbit/bun';

const { defineRoutes, attachRoutes } = createApplication();

const middlewareRoutes = defineRoutes((app) => [
    app.route('*', '/*', (request) => {
        if (request.headers.get('x-api-key') !== '123') throw new Error('Invalid API key');
    }),
]);

const appRoutes = defineRoutes((app) => [
    app.get('/', () => {
        return { hello: 'world' };
    }),
]);

Bun.serve({
    port: 3000,
    fetch: attachRoutes(middlewareRoutes, appRoutes),
});