Exa Local Dev Loop
Overview
Set up a fast, reproducible local development workflow for Exa integrations. Covers project structure, mock responses for unit tests, integration test patterns, and hot-reload configuration.
Prerequisites
exa-js installed and EXA_API_KEY configured
- Node.js 18+ with npm/pnpm
vitest for testing (or jest)
Instructions
Step 1: Project Structure
my-exa-project/
├── src/
│ ├── exa/
│ │ ├── client.ts # Singleton Exa client
│ │ ├── search.ts # Search wrappers
│ │ └── types.ts # Typed interfaces
│ └── index.ts
├── tests/
│ ├── exa.unit.test.ts # Mock-based unit tests
│ └── exa.integration.test.ts # Real API tests (needs key)
├── .env.local # Local secrets (git-ignored)
├── .env.example # Template for team
├── tsconfig.json
├── vitest.config.ts
└── package.json
Step 2: Package Setup
{
"scripts": {
"dev": "tsx watch src/index.ts",
"test": "vitest",
"test:unit": "vitest --testPathPattern=unit",
"test:integration": "vitest --testPathPattern=integration",
"build": "tsc"
},
"dependencies": {
"exa-js": "^1.0.0"
},
"devDependencies": {
"tsx": "^4.0.0",
"vitest": "^2.0.0",
"typescript": "^5.0.0"
}
}
Step 3: Mock Exa for Unit Tests
import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("exa-js", () => {
return {
default: vi.fn().mockImplementation(() => ({
search: vi.fn().mockResolvedValue({
results: [
{ url: "https://example.com/1", title: "Test Result 1", score: 0.95 },
{ url: "https://example.com/2", title: "Test Result 2", score: 0.87 },
],
}),
searchAndContents: vi.fn().mockResolvedValue({
results: [
{
url: "https://example.com/1",
title: "Test Result 1",
score: 0.95,
text: "This is the full text content of the page.",
highlights: ["Key excerpt from the page"],
summary: "A summary of the page content.",
},
],
}),
findSimilar: vi.fn().mockResolvedValue({
: [
{ : , : , : },
],
}),
: vi.().({
: [
{ : , : , : },
],
}),
})),
};
});
;
(, {
: ;
( {
exa = ();
});
(, () => {
result = exa.(, { : });
(result.).();
(result.[].).();
});
(, () => {
result = exa.(, { : });
(result.[].).();
(result.[].).();
});
});
Step 4: Integration Tests (Real API)
import { describe, it, expect } from "vitest";
import Exa from "exa-js";
const describeWithKey = process.env.EXA_API_KEY
? describe
: describe.skip;
describeWithKey("Exa Integration", () => {
const exa = new Exa(process.env.EXA_API_KEY!);
it("should execute a basic search", async () => {
const result = await exa.search("test connectivity", { numResults: 1 });
expect(result.results.length).toBeGreaterThanOrEqual(1);
expect(result.results[0].url).toMatch(/^https?:\/\//);
}, 10000);
it("should return text content", async () => {
const result = await exa.searchAndContents(, {
: ,
: { : },
});
(result.[].).();
(result.[].!.).();
}, );
(, () => {
result = exa.(, {
: ,
});
(result..).();
}, );
});
Step 5: Environment Configuration
set -euo pipefail
cat > .env.example << 'EOF'
EXA_API_KEY=
EOF
cp .env.example .env.local
echo "EXA_API_KEY=your-key-here" > .env.local
Error Handling
| Error | Cause | Solution |
|---|
Cannot find module 'exa-js' | Not installed | Run npm install exa-js |
| Test timeout | Slow API response | Increase vitest timeout to 15000ms |
| Mock not applied | Import order issue | Ensure vi.mock() is before imports |
| Integration test fails in CI | No API key secret | Add EXA_API_KEY to CI secrets or skip |
Examples
Vitest Config for Exa Projects
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "node",
testTimeout: 15000,
setupFiles: ["dotenv/config"],
},
});
Resources
Next Steps
See exa-sdk-patterns for production-ready code patterns.