Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Are they any Middleware like express with a next() callback ? #109

Closed
gungun974 opened this issue Sep 9, 2023 · 8 comments
Closed

Comments

@gungun974
Copy link

I have used express for a long time and one important thing in express for me are middleware.

Middleware are easy to use and just required a callback that will call the next middleware (util it reach the router).

I try to understand how plugin and onRequest hook works but I didn't manage how I can recreate that behavior.

Is it possible to create middleware with a "next()" callback or if that is not possible. How can I create a middleware that call a promise ?

@SaltyAom
Copy link
Member

SaltyAom commented Sep 9, 2023

There's no next() in Elysia, we rely on async-await.
Besides onRequest, you can just use async to handle promises.

@gungun974
Copy link
Author

Well...

Actually, to explain my problem, I'm trying to find a simple way for me to create a sort of global store that allows me to store the context of a route. This way, later in my presentation code, I can bypass the need to pass variables like which route my API was hit on. (This is totally inspire from Laravel Auth::user())

So, NodeJS (and therefore Bun) has the AsyncLocalStorage from node:async_hooks.
The problem with this solution is that I need to do this to contextualize the execution thread with a dedicated store.

asyncLocalStorage.run(new Map(), () => {
    next();
});

So the issue here is that with a promise, I can at best return a value in onRequest.
So I'm afraid that for this, I will have to create some kind of wrapper on each of my routes to inject the context.

If by chance you have a more elegant solution than wrapping all the "get"
I'm all opens ^^.

@SaltyAom
Copy link
Member

SaltyAom commented Sep 9, 2023

How about async derive

@gungun974
Copy link
Author

So the idea is there except that the problem is that here we only touch the context of the Elysia query and not physically launch, as explained here, an "independent storage context" which persists the same context for any following asynchronous calls.

https://nodejs.org/api/async_context.html

So obviously afterwards I could pass the context object to each function up to my presentation layer but the basic idea was to avoid mixing the profession with this kind of thing

If you are curious what a minimal implementation with Elysia, here my demo ^^

import { Elysia, t } from "elysia";

import { AsyncLocalStorage } from "node:async_hooks";

export const asyncLocalStorage = new AsyncLocalStorage<Map<string, any>>();

const app = new Elysia()
  .get(
    "/",
    async ({ query }) => {
      console.log(query.name);

      return asyncLocalStorage.run(new Map(), async () => {
        const store = asyncLocalStorage.getStore();

        if (!store) {
          return "error";
        }

        store.set("name", query.name);

        return await test();
      });
    },
    {
      query: t.Object({
        name: t.String(),
      }),
    },
  )
  .listen(3008);

async function test() {
  await new Promise((f) => setTimeout(f, 1000));

  const store = asyncLocalStorage.getStore();

  if (!store) {
    return "error";
  }

  const name = store.get("name");

  return name;
}

console.log(
  `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);

You can try to send curl 'http://localhost:3008/?name=A' and curl 'http://localhost:3008/?name=B' with some delay between and see the response is not mix

@hiep1998vnhn11
Copy link

hiep1998vnhn11 commented Sep 10, 2023

Hi, this is my solution:

app.get('/me', this.me.bind(this), {
      beforeHandle: async ({ jwt, set, request }) => {
        const token = request.headers.get('authorization')?.split(' ')[1]
        const user = token ? await jwt.verify(token) : null
        if (!user) {
          set.status = 401
          return 'Unauthorized'
        }
        request.user = user
      },
    })

@gungun974
Copy link
Author

Hi, this is my solution:

app.get('/me', this.me.bind(this), {
      beforeHandle: async ({ jwt, set, request }) => {
        const token = request.headers.get('authorization')?.split(' ')[1]
        const user = token ? await jwt.verify(token) : null
        if (!user) {
          set.status = 401
          return 'Unauthorized'
        }
        request.user = user
      },
    })

I don't think that solves my bottom problem to create an AsyncLocalStorage, however I'm curious to know what this.me.bind(this) is in your code.

@gungun974
Copy link
Author

Well...

Actually, to explain my problem, I'm trying to find a simple way for me to create a sort of global store that allows me to store the context of a route. This way, later in my presentation code, I can bypass the need to pass variables like which route my API was hit on. (This is totally inspire from Laravel Auth::user())

So, NodeJS (and therefore Bun) has the AsyncLocalStorage from node:async_hooks. The problem with this solution is that I need to do this to contextualize the execution thread with a dedicated store.

asyncLocalStorage.run(new Map(), () => {
    next();
});

So the issue here is that with a promise, I can at best return a value in onRequest. So I'm afraid that for this, I will have to create some kind of wrapper on each of my routes to inject the context.

If by chance you have a more elegant solution than wrapping all the "get" I'm all opens ^^.

Well I re read the Node documentation about AsyncLocalStorage and I miss I can create an AsyncLocalStorage with asyncLocalStorage.enterWith(store)
https://nodejs.org/api/async_context.html#asynclocalstorageenterwithstore

It's technically experimental and do some strange behavior compare to normal asyncLocalStorage.run but I tested and with onRequest I can create my context and share everywhere after.

So my strange technical need have been solve.

@wenerme
Copy link

wenerme commented Nov 27, 2023

like this #315 , create a small pr for override fetch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants