Skip to content

Optimistic Updates

Problem

A user list should update immediately while an HTTP write is pending, then reconcile success or restore previous state on failure.

Solution

Keep the optimistic transaction in the application state owner and use Courier only for transport.

ts
import { createCourier } from '@vielzeug/courier';

type User = { id: number; name: string };
const courier = createCourier({ baseUrl: '/api' });
let users = await courier.get<User[]>('/users');

async function renameUser(id: number, name: string): Promise<User> {
  const previous = users;
  users = users.map((user) => (user.id === id ? { ...user, name } : user));

  try {
    const saved = await courier.patch<User>('/users/{id}', {
      body: { name },
      params: { id },
    });
    users = users.map((user) => (user.id === saved.id ? saved : user));
    return saved;
  } catch (error) {
    users = previous;
    throw error;
  }
}

Pitfalls

  • Serialize or version overlapping writes before using whole-state rollback.
  • Keep rollback and reconciliation in the state owner, not transport middleware.
  • Use Postmaster when writes must survive reloads or offline periods.
  • Use a dedicated server-state cache when keyed invalidation and shared optimistic state are primary requirements.