vercel-labs/emulate: local API services for isolated tests
Local API emulation for CI and no-network sandboxes
At a glance
- What is it?
- emulate provides stateful local replacements for 14 third-party APIs, with fixed loopback ports, custom TypeScript emulators, and test-friendly lifecycle controls. It is aimed at CI jobs and no-network sandboxes where tests must exercise provider workflows without contacting production services.
- Who is it for?
- Adopt emulate when a CI runner or sandbox needs deterministic, stateful substitutes for the listed provider APIs and your tests fit its local HTTP model. Do not treat it as a substitute for contract tests against the real providers, because the README does not establish behavioral parity across every endpoint.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Star history
- vercel-labs/emulate1,946 ★ · +27
| Repository | 2026-10-17 | 2026-10-23 |
|---|---|---|
| vercel-labs/emulate | 1.9k | 1.9k |
27 Stars in the last 6 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Why vercel-labs/emulate targets CI without network access
vercel-labs/emulate addresses a specific test-infrastructure problem: application tests often need provider behavior such as OAuth callbacks, Stripe webhooks, Slack events, email delivery, or identity flows, while the test runner cannot call hosted APIs. Calling real services makes tests network-dependent and exposes them to external state. A conventional mock often goes in the opposite direction by returning only the response a test anticipates. The README describes emulate as a local drop-in replacement with stateful, production-fidelity API emulation, explicitly distinguishing it from mocks.
The intended users are teams running CI or operating no-network sandboxes. They can keep the application under test pointed at localhost while provider workflows remain stateful. The built-in services are Vercel, GitHub, Google, Slack, Apple, Microsoft, Okta, AWS, Resend, Stripe, MongoDB Atlas, Clerk, Linear, and Twilio. That breadth makes emulate more useful than a single test double, although it does not mean the package provides complete models of every vendor API.
The services bind to IPv4 loopback by default, which is an important security and isolation boundary. Changing the host to `0.0.0.0` can make them reachable over the network, so that option should be selected deliberately. The README labels the project as a Vercel Labs experiment. That status matters when adopting it for critical infrastructure, even though the repository is not archived and its last push was on 2026-10-15.
The loopback service map and request flow
With no configuration, `npx emulate` starts the services on consecutive ports beginning at `4000`. Vercel occupies `4000`, GitHub occupies `4001`, and Google through Twilio continue through `4013`. The client does not need a discovery service or a remote gateway. It sends normal HTTP requests to the appropriate localhost URL, and the selected emulator reads and updates its state while returning a provider-shaped response.
The base port matters when several services are enabled because each following service increments it. A test suite can therefore encode provider base URLs independently of how many unrelated services are running. The `--service` option narrows the process to named services, which reduces the startup surface and makes missing routes easier to attribute to the selected emulator.
Several protocol details go beyond basic JSON response matching. Stripe webhook requests include a `Stripe-Signature` header when a webhook secret is configured, with the signature calculated over the timestamp and raw request body. Slack callbacks use `X-Slack-Request-Timestamp` and `X-Slack-Signature` when `slack.signing_secret` is present. The documented Slack construction is HMAC-SHA256 over `v0:<timestamp>:<raw-body>`, so receiver-side verification must use the unparsed body. If the Slack secret is absent or empty, existing event subscriptions remain unsigned.
Resend adds replay semantics through a 24-hour `Idempotency-Key`. Repeated requests to `POST /emails` or `POST /emails/batch` return the original email IDs without creating duplicate emails or webhooks. These features indicate that emulate is designed around provider-specific request handling, not only endpoint stubs. The README does not enumerate every built-in route or claim that all provider edge cases are reproduced, so route coverage must be checked before relying on it.
Installing emulate and starting the first local service set
For a zero-configuration first run, the README gives the following command. It invokes the published `emulate` package through `npx` and starts the built-in service set without creating a config file.
npx emulateThe process should print access information for the enabled services. The first service is Vercel at `http://localhost:4000`, GitHub is at `http://localhost:4001`, and the remaining built-ins occupy ports through `4013`. An application test can use those URLs as provider endpoints during the local run.
A narrower run is easier to reason about when only one provider matters. This command enables Vercel and GitHub, and the base port remains `4000`.
npx emulate --service vercel,githubFor projects that will commit a reusable configuration or generate service secrets, the package can be installed as a development dependency.
npm install -D emulateThe CLI can also generate a starter configuration for all services or for one named service.
npx emulate initThe README does not define a required service credential for every emulator, so the absence of an authentication error after startup is not proof that every provider flow is configured. Teams should inspect the printed service URLs and compare the relevant routes with the application before turning a manual run into a CI job. `npx emulate list` is the documented command for enumerating available services.
Custom TypeScript emulators and deterministic state control
Teams can define an emulator for an HTTP API that is not built in. A custom provider declares its initial state and routes through `defineEmulator`, while `createEmulator` returns an API object for direct requests, snapshots, resets, and closure. The documented inventory example mutates stock, rejects depleted inventory with a `409`, and can be checkpointed and restored without making an HTTP request because it uses `listen: false`.
import { defineEmulator, createEmulator } from 'emulate'
const acme = defineEmulator({
name: 'acme',
state: () => ({ anvils: 100 }),
setup({ app, state }) {
app.post('/orders', (c) => {
if (!state.anvils) return c.json({ error: 'sold_out' }, 409)
state.anvils -= 1
return c.json({ shipped: 'anvil' }, 201)
})
},
})
const api = await createEmulator({ service: acme, listen: false })
await api.request('/orders', { method: 'POST' })
const checkpoint = api.snapshot()
await api.reset()
await api.restore(checkpoint)
await api.close()The scaffold workflow creates a config entry, routes, and a runnable Node test. It accepts discovered YAML, JSON, TypeScript, or JavaScript configuration, while an unusual executable config may require manually adding the printed import and service entry.
npm install -D emulate
npx emulate init --custom inventory
npx emulate start --watchState isolation is explicit. Seeds replace the complete initial state, reset restores the captured seed, and independent instances do not share state. Watch mode creates a new baseline after a successful reload and resets the current run. Persistence is opt-in and uses versioned snapshots, but the README does not document cross-process locking. That omission matters for parallel CI workers sharing a persistence location.
Node requirements, persistence boundaries, and upgrade cost
The repository requires Node `>=24` and pnpm `>=11 <12` for monorepo development. TypeScript loading has a material runtime boundary. Node 26 supports erasable TypeScript only, so enums and parameter properties must be compiled to JavaScript before they are loaded. Node 24 also supports native TypeScript transforms, but applications using older Node versions are outside the declared engine range.
The main package is published through npm, while the repository itself is a private `emulate-monorepo` workspace containing `packages/`, `apps/`, tests, and examples. An npm development dependency does not imply that adopting the package requires cloning this workspace. Consumers should not infer that all monorepo scripts are part of the package API.
Upgrade frequency is currently visible in the release record. Versions `v0.12.0`, `v0.12.1`, and `v0.12.2` were released on 2026-09-24, 2026-10-02, and 2026-10-13 respectively. The repository is not archived, and its last push was on 2026-10-15. This supports expecting continued repository work, but it does not establish API stability. The version remains below `1.0.0`, and the Vercel Labs experiment label is another reason to review changes to configuration keys and custom-emulator APIs during upgrades.
Optional persistence introduces storage and compatibility work. Versioned snapshots support controlled restoration, but cross-process locking is absent. Teams using shared storage must therefore keep snapshots scoped so parallel workers cannot overwrite one another. The README does not document snapshot retention, merge semantics, or compatibility policy across package versions, so those points need direct verification.
The package uses the Apache-2.0 license. The repository also contains `THIRD_PARTY_NOTICES.md`, which is relevant when redistributing a deployment or examining bundled dependencies. This is not legal advice; license review should cover the exact distributed package and included notices.
Where emulate is the wrong testing layer
The clearest limitation is coverage uncertainty. The README names 14 built-in services and describes several detailed behaviors, but it does not publish a complete endpoint matrix in the available documentation. Adopting emulate based only on a provider logo would therefore be premature. If the application depends on an obscure provider endpoint, unusual validation rule, pagination behavior, or error format, confirm that route before replacing the hosted dependency.
Stateful emulation also differs from testing the real service contract. emulate can make local execution repeatable, but a local implementation cannot prove that the hosted provider accepts the same request, returns the same behavior under load, or applies the same account restrictions. The repository's examples cover selected OAuth, Resend magic links, Stripe Checkout, Twilio verification, and other workflows, yet those examples are not a general guarantee of full provider parity.
Network isolation may also expose a workflow problem. If production code resolves provider URLs from configuration that the CI harness cannot override, simply starting the emulators will not route requests to localhost. The first integration should therefore prove that the application reads the intended endpoint configuration. If tests need external state coordination, real sandbox credentials, or validation that depends on provider infrastructure, emulate is the wrong layer.
Custom emulators carry implementation cost. Every route and state transition must be written and maintained by the adopting team. Shared persistence is another boundary because there is no cross-process locking. Independent instances and `port: 0` are useful for parallel unit-level tests, while external shared services are better tested through a dedicated contract suite.
emulate compared with mocks and hosted provider sandboxes
The practical alternative is conventional application mocks, but the approach differs. A mock is usually injected at a client boundary and returns values selected by each test. emulate instead listens as an HTTP service, follows provider-specific headers and routes, and retains state across requests. That makes it closer to the application's actual transport path and allows one emulator process to serve tests that resemble normal API calls.
Another alternative is a hosted provider sandbox. A real sandbox can validate requests against the provider's current infrastructure, but it introduces network access, credentials, external account state, and possible service availability. emulate trades those properties for local control. Its fixed localhost ports and state reset behavior are suited to CI and no-network environments where external calls are unavailable or undesirable.
The tradeoff is fidelity ownership. A mock is cheap when a test only needs one response. emulate becomes more expensive when a workflow needs multiple routes, signatures, callbacks, replay behavior, or evolving state, because its routes and state must accurately represent the portion of the provider being exercised. A hosted sandbox avoids writing that local model but cannot satisfy a strict no-network requirement.
The choice therefore depends on the test boundary. Use conventional mocks for narrow unit tests, emulate when the application needs provider-shaped HTTP behavior across requests, and provider-controlled validation when the question is whether the real service currently accepts the integration. A combined suite can cover local state behavior and hosted contract checks, although this README does not describe a hosted-contract adapter.
Editorial conclusion
Adopt emulate when a CI runner or sandbox needs deterministic, stateful substitutes for the listed provider APIs and your tests fit its local HTTP model. Do not treat it as a substitute for contract tests against the real providers, because the README does not establish behavioral parity across every endpoint. Before adoption, verify the required service and routes, Node compatibility, secret handling, persistence needs, and the provider-specific behavior most likely to affect a release gate.
Frequently asked questions
How do I install and start emulate with its built-in services?
Run `npx emulate`. The built-in services start without a config file, beginning with Vercel on `http://localhost:4000` and continuing on consecutive ports through Twilio on `http://localhost:4013`.
Can emulate run in a no-network CI sandbox?
Yes. emulate is designed for CI and no-network sandboxes, and its services listen on IPv4 loopback by default, so applications can call local HTTP endpoints instead of hosted provider APIs.
How do I create a custom API emulator in TypeScript?
Install `emulate`, run `npx emulate init --custom inventory`, and adapt the generated TypeScript routes and state. The scaffold can then be run with `npx emulate start --watch` and reused in Node tests or framework adapters.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/vercel-labs-emulate)Star history chart for your README
[](https://hysenlabs.com/projects/vercel-labs-emulate)Was this useful?