Skip to content

get-convex/convex-waitlist

Repository files navigation

Convex Waitlist

An easy to adopt waitlist implementation using Convex. Read the detailed guide to this implementation on Stack.

Screenshot of user on a waitlist

Overview

This repo includes a full-fledged implementation of a waitlist which can be used to protect your app against surges in demand.

Demo

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.

Running the App

npm install
npm run dev

Configuration

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.

Adding waitlist to an existing app

  1. Follow one of the Convex quickstarts to get Convex set up.

    1. You don’t need to use Convex for anything else other than the waitlist (but you really should).
  2. Copy the convex/waitlist folder from this repo into your convex folder

  3. 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;
  4. 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
    });
  5. 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 ...
      },
    });
  6. 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 ...
      },
    });
  7. Implement your UI. If you're using React:

    1. Copy the src/waitlist folder from this repo into your src/app/pages/lib/whatever client-side source folder
    2. 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 server
      • whileWaiting to render the UI when the user is waiting
      • sessionId 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.

And you're done!

What is Convex?

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.