This walkthrough adds Jev DevTools to an existing React application with a Node server. You need React 18/19, Node 22.12+, and TypeSafe SDK 0.7.x. For the App Router, use the Next.js guide.
1. Install the package#
npm install jev-devtools @typesafe-ai/sdkAlready have a TypeSafe client? Replace its construction with the instrumented client below. Your existing systemOne calls keep their SDK behavior.
2. Create a server adapter#
// server/jev.ts — Node only
import { createJevDevtools } from "jev-devtools/server";
export const inspector = createJevDevtools({
enabled: process.env.NODE_ENV === "development",
origin: "http://localhost:3000", // Must match the browser URL exactly.
});
export const jev = inspector.createClient({
apiKey: process.env.TYPESAFE_API_KEY,
});Create an independent Node server adapter. Share it between your HTTP endpoints and instrumented clients.
createJevDevtools(options)- enabled
- Opt into server capture and inspector endpoints. Defaults to false; disabling capture does not stop inference.
- origin
- The exact browser origin, including scheme, hostname, and port.
- endpoint
- The inspector URL path. Defaults to /api/jev-devtools; must match the browser component.
- limit
- Maximum retained request traces across sessions. Defaults to 50; clamped to 1–200.
- redact
- Transform the captured copy before storage or streaming. Does not change SDK inputs or application values.
- authorize
- Your application access check. Required for an enabled non-loopback origin.
- getSession
- Derive a trusted recording scope from the request. Defaults to the shared local scope.
Opt into server capture and inspector endpoints. Defaults to false; disabling capture does not stop inference.
enabledThe exact browser origin, including scheme, hostname, and port.
originCreate an instrumented TypeSafe client. Captures systemOne calls when recording is enabled; otherwise returns an ordinary SDK client. Keep credentials on the server.
adapter.createClient(options, metadata?)- label
- Optional capture label. Defaults to Jev request.
- source
- Capture label: sample or live. Defaults to live. This does not switch transports or prevent inference.
API reference: createJevDevtools() · adapter.createClient()
Keep this module on the server. Put TYPESAFE_API_KEY in your server environment, never in a VITE_ or NEXT_PUBLIC_ variable. This example uses live inference when you call it; it needs a real key. For a no-key first run, use the independent samples.
The origin must be the URL you open in the browser, including scheme, hostname, and port. localhost and 127.0.0.1 are different origins.
3. Connect the HTTP routes#
In Express or Connect, mount the middleware at the root, before your fallback handler:
import { inspector } from "./server/jev.js";
app.use(inspector.middleware);
// Your application's HTTP handlers use the client from server/jev.ts.
// Call inspector.dispose() when the server shuts down.Mount at the application root to serve inspector endpoints and their subpaths. Unrelated routes call next().
app.use(adapter.middleware)API reference: adapter.middleware
Here app is your existing Express/Connect application. Bind local development servers to loopback. If your frontend runs on a different port, proxy /api/jev-devtools and your inference route through the frontend origin. Preserve the full path and Host header; see access controls.
For a Vite-only local example, the independent React + Vite app mounts the adapter in a Vite development plugin and uses fixed responses.
4. Mount the drawer#
import { JevDevtools } from "jev-devtools";
export function Root() {
return (
<>
<main>Your application</main>
<JevDevtools enabled={import.meta.env.DEV} defaultOpen={false} />
</>
);
}Mount the inspector once near your application root. It connects after mounting when enabled; it renders nothing during SSR.
<JevDevtools enabled defaultOpen={false} />- enabled
- Load and connect the browser inspector. Defaults to false; enable the server adapter separately.
- defaultOpen
- Whether the drawer starts expanded. Defaults to true; false starts at the launcher.
- endpoint
- Same-origin inspector path. Defaults to /api/jev-devtools; must match the server adapter.
- redactAdditionalSensitiveData
- Mask supported personal fields in the display. Does not remove captured data. Unreleased; defaults to true.
Load and connect the browser inspector. Defaults to false; enable the server adapter separately.
enabledWhether the drawer starts expanded. Defaults to true; false starts at the launcher.
defaultOpenAPI reference: JevDevtools
import.meta.env.DEV is Vite's development flag. Other frameworks should use their equivalent. Both the server and the component are disabled by default; enable each explicitly during development.
5. Make a request#
Call this function from an existing application HTTP handler:
// An application handler on your Node server.
import { choice } from "@typesafe-ai/sdk";
import { jev } from "./server.js";
export async function classifyMessage(message: string) {
return jev.systemOne({
state: { message },
questions: {
team: choice("Which team can help?", {
billing: "Charges and payments",
delivery: "Shipping and tracking",
other: "Everything else",
}),
},
});
}Trigger the handler from your app, open the Jev DevTools launcher, and select the recorded request. You should see your message in Context and the returned team in Questions. Inspect the returned choice directly; the inspector does not substitute its own highest-probability result.
Verify your integration#
- Make one request through the instrumented client and confirm it appears.
- Open Questions, Context, and Raw to compare the capture with your application result.
- Close and reopen the drawer; your selection should remain while the trace is retained.
- Confirm production disables both the component and adapter. SDK requests still work when recording is off.
An empty drawer usually means the route and the inference handler use different adapter instances, or the request came from a different client. See troubleshooting.
Look up an API#
The API glossary lists each method’s inputs and return value. Ready to connect multiple steps? Start with Flows from scratch.