UNPKG

@fastify/rate-limit

Version:

A low overhead rate limiter for your routes

186 lines (169 loc) 4.14 kB
'use strict' // Example of a Custom Store using Sequelize ORM for PostgreSQL database // Sequelize Migration for "RateLimits" table // // module.exports = { // up: (queryInterface, { TEXT, INTEGER, BIGINT }) => { // return queryInterface.createTable( // 'RateLimits', // { // Route: { // type: TEXT, // allowNull: false // }, // Source: { // type: TEXT, // allowNull: false, // primaryKey: true // }, // Count: { // type: INTEGER, // allowNull: false // }, // TTL: { // type: BIGINT, // allowNull: false // } // }, // { // freezeTableName: true, // timestamps: false, // uniqueKeys: { // unique_tag: { // customIndex: true, // fields: ['Route', 'Source'] // } // } // } // ) // }, // down: queryInterface => { // return queryInterface.dropTable('RateLimits') // } // } const fastify = require('fastify')() const Sequelize = require('sequelize') const databaseUri = 'postgres://username:password@localhost:5432/fastify-rate-limit-example' const sequelize = new Sequelize(databaseUri) // OR // const sequelize = new Sequelize('database', 'username', 'password'); // Sequelize Model for "RateLimits" table // const RateLimits = sequelize.define( 'RateLimits', { Route: { type: Sequelize.TEXT, allowNull: false }, Source: { type: Sequelize.TEXT, allowNull: false, primaryKey: true }, Count: { type: Sequelize.INTEGER, allowNull: false }, TTL: { type: Sequelize.BIGINT, allowNull: false } }, { freezeTableName: true, timestamps: false, indexes: [ { unique: true, fields: ['Route', 'Source'] } ] } ) function RateLimiterStore (options) { this.options = options this.route = '' } RateLimiterStore.prototype.routeKey = function routeKey (route) { if (route) this.route = route return route } RateLimiterStore.prototype.incr = async function incr (key, cb) { const now = new Date().getTime() const ttl = now + this.options.timeWindow const cond = { Route: this.route, Source: key } const RateLimit = await RateLimits.findOne({ where: cond }) if (RateLimit && parseInt(RateLimit.TTL, 10) > now) { try { await RateLimit.update({ Count: RateLimit.Count + 1 }, cond) cb(null, { current: RateLimit.Count + 1, ttl: RateLimit.TTL }) } catch (err) { cb(err, { current: 0 }) } } else { sequelize.query( `INSERT INTO "RateLimits"("Route", "Source", "Count", "TTL") VALUES('${this.route}', '${key}', 1, ${RateLimit?.TTL || ttl}) ON CONFLICT("Route", "Source") DO UPDATE SET "Count"=1, "TTL"=${ttl}` ) .then(() => { cb(null, { current: 1, ttl: RateLimit?.TTL || ttl }) }) .catch(err => { cb(err, { current: 0 }) }) } } RateLimiterStore.prototype.child = function child (routeOptions = {}) { const options = Object.assign(this.options, routeOptions) const store = new RateLimiterStore(options) store.routeKey(routeOptions.routeInfo.method + routeOptions.routeInfo.url) return store } fastify.register(require('../../fastify-rate-limit'), { global: false, max: 10, store: RateLimiterStore, skipOnError: false } ) fastify.get('/', { config: { rateLimit: { max: 10, timeWindow: '1 minute' } } }, (_req, reply) => { reply.send({ hello: 'from ... root' }) }) fastify.get('/private', { config: { rateLimit: { max: 3, timeWindow: '1 minute' } } }, (_req, reply) => { reply.send({ hello: 'from ... private' }) }) fastify.get('/public', (_req, reply) => { reply.send({ hello: 'from ... public' }) }) fastify.listen({ port: 3000 }, err => { if (err) throw err console.log('Server listening at http://localhost:3000') })