redwoodjs-com-archive
redwoodjs-com-archive copied to clipboard
rateLimit directive
Howdy, I'm almost done implementing rate limiting using the envelop plugin. Wanted to start throwing together docs for this so others can use them.
Todo:
- [ ] Finish Redis store
- [ ] Test the final implementation
Docs for using @envelop/rate-limiter
📖 Adpated for Redwood from https://www.envelop.dev/plugins/use-rate-limiter
First install the necessary packages:
yarn add @envelop/rate-limiter graphql-rate-limit
Generate the rateLimit
directive, and choose "Validator" when prompted:
yarn rw g directive rateLimit
We'll need to make a few changes to the directive, so it can accept arguments:
// api/src/directives/rateLimit/rateLimit.js
export const schema = gql`
"""
Use @rateLimit to validate access to a field, query or mutation.
"""
- directive @rateLimit on FIELD_DEFINITION
+ directive @rateLimit(max: Int, window: String, message: String) on FIELD_DEFINITION
Then add the directive to your SDLs e.g.:
type Query {
posts: [Post]! @rateLimit(
window: "5s", // time interval window for request limit quota
max: 10, // maximum requests allowed in time window
message: "Too many calls!" // quota reached error message
)
# unlimitedField: String
}
Since our function is serverless, we need to keep in mind that using memory store is probably not the best idea. Instead, let's use Redis to manage our IP address logging. Create a redis entity:
// api/src/lib/redis.js
// https://github.com/teamplanes/graphql-rate-limit#redis-store-usage
// TODO
Lastly, add the plugin to the graphql server, and specify the store
option to use our Redis instance:
// api/src/functions/graphql.js
import { useRateLimiter } from '@envelop/rate-limiter'
import { createRateLimitDirective, RedisStore } from 'graphql-rate-limit';
import redis from "src/lib/redis"
const identifyFn = async (context) => context.request.ip
export const handler = createGraphQLHandler({
extraPlugins: [
useRateLimiter({
identifyFn,
// https://github.com/teamplanes/graphql-rate-limit#redis-store-usage
store: new RedisStore(redis.createClient())
}),
],
@pi0neerpat Thanks for taking this on -- in fact after @dac09 and I implemented the Validator and Transformer directives, I realized that "traditional" directives might collide -- but then I realized, maybe not!
I hoped (but did not confirm) that since the directive in the schema -- which is declared by the plugin -- is neither a Validator or Transformer:
https://github.com/redwoodjs/redwood/blob/91f907c1b6d3bc89d06bc1333b4394439f4f8803/packages/graphql-server/src/plugins/useRedwoodDirective.ts#L135
I hoped that the useRedwoodDirective
would just let it work as normal.
But regardless:
@rateLimit(max: Int, window: String, message: String)
You can pass in arguments. They are available as the directiveArgs
:
https://github.com/redwoodjs/redwood/blob/91f907c1b6d3bc89d06bc1333b4394439f4f8803/packages/graphql-server/src/plugins/useRedwoodDirective.ts#L18
That's how requireAuth can look at the roles.
As for Redis, have a look at ioredis.
I used it to implement the responseCache for Redis here:
https://github.com/dotansimha/envelop/blob/4cae3427244f683683ffee5046303b76a926dc43/packages/plugins/response-cache-redis/src/redis-cache.ts#L1
Also, have you seen the redis store provided by rate-limiter?
https://github.com/teamplanes/graphql-rate-limit/blob/master/src/lib/redis-store.ts