An easy to adopt waitlist implementation using Convex. Read the detailed guide to this implementation on Stack.
This repo includes a full-fledged implementation of a waitlist which can be used to protect your app against surges in demand.
- The
Waitlist
component renders a replacement UI when the user must wait before they can use the app, as demonstrated inApp.tsx
- The waitlist backend keeps track of sessions and the number of active
sessions, as defined in
convex/waitlist/schema.ts
- The waitlist is updated periodically via a cron defined in
convex/waitlist/crons.ts
- The read endpoints powering the UI and all database queries are defined in
convex/waitlist/read.ts
- The waitlist session creation and refresh logic is in
convex/waitlist/write.ts
You can play with the live demo. It's been configured to allow only 3 users at a time to have access. You can open multiple browser tabs to create multiple user sessions.
npm install
npm run dev
The implementation uses the following environment variables, which can be
configured on the Convex dashboard (run npx convex dashboard
to open it):
ACTIVE_SESSIONS_COUNT_LIMIT
how many active (not-waiting) sessions there can be at the same time. Defaults to 100, but you should really change this based on the number of users your app can handle.WAITLIST_UPDATE_INTERVAL_SECONDS
how often the waitlist should be updated based on active sessions becoming inactive. Defaults to every 60 seconds.ACTIVE_SESSION_TIMEOUT_SECONDS
for how long an active session can be inactive before it expires. Defaults to 5 minutes. Make this shorter if your app is highly interactive and you want users to move off of the waitlist faster.WAITING_SESSION_TIMEOUT_SECONDS
how long after a user waiting user leaves should their session expire. Defaults to 1 minute. Make this longer if you expect users to wait a long time and you want them to retain their position in the line.
-
Follow one of the Convex quickstarts to get Convex set up.
- You don’t need to use Convex for anything else other than the waitlist (but you really should).
-
Copy the
convex/waitlist
folder from this repo into yourconvex
folder -
Set up crons. Adds the waitlist crons setup to your
convex/crons.ts
file:import { cronJobs } from "convex/server"; import { setupWaitlistCrons } from "./waitlist/crons"; const crons = cronJobs(); setupWaitlistCrons(crons); // ...your other crons, if any export default crons;
-
Set up the schema. Add the waitlist tables to your
convex/schema.ts
file:import { defineSchema, defineTable } from "convex/server"; import { waitlistTables } from "./waitlist/schema"; export default defineSchema({ ...waitlistTables, // ...your other tables, if any });
-
Protect your queries (read endpoints) by checking that the current session is active:
import { v } from "convex/values"; import { query } from "./_generated/server"; import { validateSessionIsActive } from "./waitlist/read"; export const someRead = mutation({ args: { // ... some arguments // any string will do as the user/session identifier sessionId: v.string(), }, handler: async (ctx, args) => { // Check that the user is not still waiting await validateSessionIsActive(ctx, args.sessionId); // ... do whatever you need to do ... }, });
-
Protect your mutations (write endpoints) by checking that the current session is active, and refresh the
lastActive
timestamp. See the article for more details:import { v } from "convex/values"; import { mutation } from "./_generated/server"; import { validateSessionAndRefreshLastActive } from "./waitlist/write"; export const someWrite = mutation({ args: { // ... some arguments // any string will do as the user/session identifier sessionId: v.string(), }, handler: async (ctx, args) => { // Check that the user is not still waiting and // record that the user is actively using the app. await validateSessionAndRefreshLastActive(ctx, args.sessionId); // ... do whatever you need to do ... }, });
-
Implement your UI. If you're using React:
- Copy the
src/waitlist
folder from this repo into your src/app/pages/lib/whatever client-side source folder - Wrap your app in the
Waitlist
component, which takes the following props:loading
with what is shown while we’re loading the waitlist status from the serverwhileWaiting
to render the UI when the user is waitingsessionId
to identify the current user or session
example:
import { Waitlist } from "./waitlist/Waitlist"; export function App() { const sessionId = useSessionId(); return ( <Waitlist loading="Loading..." sessionId={sessionId} whileWaiting={(position, numWaiting) => ( <p> You're on the wailist, thanks for your patience! <br /> Your position in the line: {position} out of {numWaiting}.<br /> Thanks for waiting. </p> )} > <p>You're no longer waiting! Congratz!</p> </Waitlist> ); }
If you're not using React check out the implemention in src/waitlist/Watlist.tsx for inspiration for your own client implementation.
- Copy the
And you're done!
Convex is a hosted backend platform with a built-in
database that lets you write your
database schema and
server functions in
TypeScript. Server-side database
queries automatically
cache
and subscribe to data,
powering a
realtime useQuery
hook
in our React client. There are also
Python,
Rust,
ReactNative, and
Node clients, as well as a
straightforward
HTTP API.
The database support NoSQL-style documents with relationships and custom indexes (including on fields in nested objects).
The query
and
mutation
server
functions have transactional, low latency access to the database and leverage
our v8
runtime with
determinism guardrails
to provide the strongest ACID guarantees on the market: immediate consistency,
serializable isolation, and automatic conflict resolution via
optimistic multi-version concurrency control
(OCC / MVCC).
The action
server functions have
access to external APIs and enable other side-effects and non-determinism in
either our optimized v8
runtime
or a more
flexible node
runtime.
Functions can run in the background via scheduling and cron jobs.
Development is cloud-first, with hot reloads for server function editing via the CLI. There is a dashbord UI to browse and edit data, edit environment variables, view logs, run server functions, and more.
There are built-in features for reactive pagination, file storage, reactive search, https endpoints (for webhooks), streaming import/export, and runtime data validation for function arguments and database data.
Everything scales automatically, and it’s free to start.