nestjs / throttler

A rate limiting module for NestJS to work with Fastify, Express, GQL, Websockets, and RPC 🧭
https://nestjs.com
MIT License
617 stars 58 forks source link

Throttle on only a single route #1900

Open milad-afkhami opened 6 months ago

milad-afkhami commented 6 months ago

Is there an existing issue that is already proposing this?

Is your feature request related to a problem? Please describe it

My challenge is how to apply throttling to a specific route without setting up a global throttle. My concern arises from the necessity to log every single request from every single user (in memory) when establishing a global throttling system, to ascertain whether the new request necessitates throttling. How can I accomplish this?

Describe the solution you'd like

Globally config it like:

imports: [
    ...
    ThrottlerModule.forRoot()
]

and use it like this on the route:

@Throttle([{ limit: 3, ttl: 60000 }])
@Get()
findAll() {
  return "List users works with custom rate limiting.";
}

Notes:

  1. It's better to remove the default keyword on the Throttle decorator
  2. It's good to add support for arrays inside the Throttle decorator
  3. I have tested the global config and added a guard with an empty config and it doesn't work.

Teachability, documentation, adoption, migration strategy

No response

What is the motivation / use case for changing the behavior?

For more customizations and more control for throttling and saving more space.

milad-afkhami commented 6 months ago

@canoir it was your company's concern too, right?

jmcdo29 commented 6 months ago

Okay, I think I see the request here. Instead of being forced to use the ThrottlerModule it would be nice to set the @UseGuards() and @Throttle() on a case-by-case basis, correct?

I think the ThrottlerModule should still be imported for the access to the storage service, but that may be movable to not be required in a dynamic module, or we just keep the forRoot as is and allow an empty function call (easiest as there's no changes there)

In the @Throttle(), I can probably allow it to take in the config or a record of name: config for sake of ease and transform it under the hood. If we were to take in either the record or the array, if the array had two configurations, both without name, the latter would overwrite the former and lead to unexpected behavior. Do you think this would make for a good DX?


As for the initial "How can I implement this?", so long as there's a config object passed to the forRoot() (something that has a ttl and limit, it can be ridiculous numbers too, e.g. [{ limit: 0, ttl: 0 }]) the n the guard should work with the overwrite from the @Throttle() decorator

Canoir commented 6 months ago

Okay, I think I see the request here. Instead of being forced to use the ThrottlerModule it would be nice to set the @UseGuards() and @Throttle() on a case-by-case basis, correct?

I think the ThrottlerModule should still be imported for the access to the storage service, but that may be movable to not be required in a dynamic module, or we just keep the forRoot as is and allow an empty function call (easiest as there's no changes there)

In the @Throttle(), I can probably allow it to take in the config or a record of name: config for sake of ease and transform it under the hood. If we were to take in either the record or the array, if the array had two configurations, both without name, the latter would overwrite the former and lead to unexpected behavior. Do you think this would make for a good DX?

As for the initial "How can I implement this?", so long as there's a config object passed to the forRoot() (something that has a ttl and limit, it can be ridiculous numbers too, e.g. [{ limit: 0, ttl: 0 }]) the n the guard should work with the overwrite from the @Throttle() decorator

I agree with you and about the name: config , I think that is great too, our need is exactly what you said, but your other answer make me a bigger question, what will happen if we pass ttl: 0 and limit 0 to global config? I mean if the guard only passes cause of 0 then there is a little bit shitty but solution to our problem even now, right?

jmcdo29 commented 6 months ago

The idea I suggested was just to make sure that there was a default config so that the @Throttle() override properly works. So long as the guard is not applied to any routes without the @Throttle() then the point is moot,. If it is, you'll find out pretty quickly :smile:

mukunda- commented 1 month ago

It took me an hour to realize that I need to have a default config block, even if I'm not using it. I'd say it should have a default if not specified.

dominic-schmid commented 1 month ago

So, I'm not sure if I'm just doing something wrong but:

How would I mark only certain routes to be throttled then? @Throttle({ long: { limit: 5, ttl: 60000 } })

mukunda- commented 1 month ago

@dominic-schmid currently it would be like this:

  1. import throttler with a default configuration provided
  2. mark your route with the @Throttle line and a @UseGuards(ThrottlerGuard)

For all routes to be affected, there is another way to apply the ThrottlerGuard globally. Otherwise, it is only going to affect specific routes marked with the ThrottlerGuard.

This issue is concerning step 1 - it would be better if there was a preconfigured "default" rather than needing to specify a dummy configuration if you are using custom limit/ttl for each route.

jvvcn commented 1 month ago

Having same problem - I do use module with default configuration, however there are routes that I would like to use different rate limit that is defined in module. Currently I cannot do it without overriding default configurations... Any ideas how to workaround it?

jmcdo29 commented 4 weeks ago

Having same problem - I do use module with default configuration, however there are routes that I would like to use different rate limit that is defined in module. Currently I cannot do it without overriding default configurations... Any ideas how to workaround it?

@jvvcn that's kind of the idea of what should be done. Is there something you'd rather be able to do?