Rate limit your SvelteKit app with Upstash Redis
Rate limiting is an important security measure for publicly exposed endpoints, especially if they perform intensive operations or call an external API that bills based on usage. In this post, I’ll show you how to rate limit your SvelteKit application using Upstash Redis.
If you want to skip to the end, the final code is on GitHub.
Setting up the project
To get started, run the following command in a terminal to scaffold a new SvelteKit project. Select the “Skeleton project” option and “TypeScript” for type-checking. Set up the rest of the options as you like — if you don’t have a preference, choose the default option.
Then follow the listed steps to install dependencies and start the dev server.
First, let’s create a <form>
and associated form action. Add the following code to your root page at /src/routes/+page.svelte
This code adds a form to the page to submit a value to our server. Create a +page.server.ts
with the following content to handle the form submission.
In this demo the form is only converting the provided text into snake case, but you can think of the performExpensiveOperation
function as standing in for a much more expensive action or API call.
This is a fairly standard SvelteKit form, so if any of this looks unfamiliar give the docs a read.
If you navigate to where the dev server is running and submit a value with the form, you should see the value transformed into snake case. For example, submitting “the quick brownFoxJumped over-the-lazy-dog” should show “the_quick_brown_fox_jumped_over_the_lazy_dog” when you submit the form.
This works great! However, there’s an issue — people can submit as many requests as they want to our endpoint. For this demo, that’s not a big deal, since it’s a simple operation. However, if we were doing something that took a lot of time, or calling another service that charged based on the number of API calls, then we would want to limit the number of requests that individual users can make.
One way to do that is with Upstash’s rate limiting SDK, which tracks the number of requests a user makes in a Redis® database and tells you if they’ve gone over the limit.
Adding rate limiting to our form action
First, set up a new Redis® database via the Upstash console and retrieve the UPSTASH_REDIS_REST_URL
and UPSTASH_REDIS_REST_TOKEN
environment variables. Place those variables in an .env
file at the root of the repo.
UPSTASH_REDIS_REST_URL=URL_GOES_HERE
UPSTASH_REDIS_REST_TOKEN=TOKEN_GOES_HERE
Then, install the necessary dependencies for Upstash.
At the top of our +page.server.ts
file, import the necessary dependencies and initialize the database and rate limiter.
A few things to note:
- We import the environment variables using SvelteKit’s $env module for environment variables. In this case we’re using static environment variables, which requires your variables to be available at build time. If this is not the case, you should use dynamic environment variables instead.
- We’re using the sliding window rate limiting strategy and allowing 5 requests every 10 seconds. The
@upstash/ratelimit
package has a variety of strategies available with different pros and cons, which you can read about in the documentation. - We initialize our Redis client when the app starts up, instead of creating a new one per-request. While doing this, we first check if we’re building the app so we don’t initialize the client when the app builds. During the build process, SvelteKit imports all our code to analyze it.
With that, we can use the rate limiter in our action and return an error response if the user has made too many requests. The rate limiter needs to group requests using an identifier — in this case, we use the IP address of the request.
You can test this out by loading up the page and clicking the submit button quickly. After a few times, you should see an error message telling you that you’ve been rate limited.
It’s important to note that this does not prevent requests to the app itself. What it does prevent is the app from doing further work once the request comes in, e.g. calling an expensive API or performing some intensive work.
Going further
In this tutorial we protected a single form action. You can apply the same method to protect load functions or server routes. If you want to rate limit requests to your entire application, you can use a custom handle hook:
You can inspect the event argument to determine whether to limit the request, for instance if you wanted to only limit requests that matched a given URL.
There is an open SvelteKit feature request for rate limiting to be provided by SvelteKit itself, though isn’t a clear solution since SvelteKit doesn’t come with a database.
This tutorial only scratched the surface of the @upstash/ratelimit
package — consult the full documentation for additional options and considerations.