Skip to content

Background Writes with waitUntil

This guide explains how to perform "fire-and-forget" database operations using Cloudflare's waitUntil method. This is particularly useful for tasks that don't need to block the response to the user, such as logging, analytics, or other non-critical writes.

How it Works

Cloudflare Workers can continue to execute code for a short period after the response has been sent to the client by using the ctx.waitUntil() method, where ctx is the ExecutionContext of your Worker's fetch handler.

By wrapping a workers-qb query promise in waitUntil, you ensure that the query is executed without making the user wait for the database operation to complete.

Note: This approach is suitable for operations where you don't need to return the result to the user.

Examples

Here are some examples of how to use waitUntil for different types of write operations.

Background insert

This is useful for logging requests, tracking events, or any other scenario where you need to add a record without delaying the user's response.

typescript
import { D1QB } from 'workers-qb';

export interface Env {
  DB: D1Database;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const qb = new D1QB(env.DB);
    const url = new URL(request.url);

    // Don't await this promise, let it run in the background
    const insertPromise = qb.insert({
      tableName: 'logs',
      data: {
        method: request.method,
        path: url.pathname,
        timestamp: new Date().toISOString(),
      },
    }).execute();

    ctx.waitUntil(insertPromise);

    return new Response('Request logged in the background!', { status: 200 });
  },
};

Background update

You can update records in the background, for example, to increment a counter or update a user's last-seen timestamp.

typescript
import { D1QB, Raw } from 'workers-qb';

export interface Env {
  DB: D1Database;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const qb = new D1QB(env.DB);
    const userId = request.headers.get('X-User-ID');

    if (userId) {
      // Don't await this promise
      const updatePromise = qb.update({
        tableName: 'users',
        data: {
          last_seen: new Raw('CURRENT_TIMESTAMP'),
        },
        where: {
          conditions: 'id = ?',
          params: userId,
        },
      }).execute();

      ctx.waitUntil(updatePromise);
    }

    return new Response('User activity updated in the background.', { status: 200 });
  },
};

Background delete

Perform cleanup operations, such as deleting expired sessions or old data, without affecting the response time.

typescript
import { D1QB } from 'workers-qb';

export interface Env {
  DB: D1Database;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const qb = new D1QB(env.DB);
    const sessionId = request.headers.get('X-Session-ID');

    if (sessionId) {
      // Don't await this promise
      const deletePromise = qb.delete({
        tableName: 'sessions',
        where: {
          conditions: 'id = ?',
          params: sessionId,
        },
      }).execute();

      ctx.waitUntil(deletePromise);
    }

    return new Response('Session invalidated in the background.', { status: 200 });
  },
};

Released under the MIT License.