This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A TypeScript-based Vercel serverless API that provides weather data from OpenWeatherMap's 3.0 One Call API and aviation METAR data from Garmin. Built with a clean, modular architecture emphasizing type safety and separation of concerns.
Prerequisites:
- Node.js >= 24.x
- Vercel CLI installed globally:
npm i -g vercel
Development:
npm start # Start Vercel dev server (http://localhost:3000)
npm run test:watch # Run tests in watch mode while developingTesting:
npm test # Run all tests
npm run test:coverage # Run tests with coverage report
npm run test:ui # Run tests with browser UICode Quality:
npm run lint # Check for linting errors
npm run lint:fix # Auto-fix linting errors
npm run format # Format all code with Prettier
npm run format:check # Check formatting without modifying files
npm run type-check # Run TypeScript type checking
npm run check # Run ALL checks (format, lint, type-check, test)Deployment:
vercel # Deploy to VercelPre-commit Hooks:
Husky automatically runs before each commit:
- Formats staged TypeScript files with Prettier
- Lints staged files with ESLint (auto-fixes when possible)
- Type-checks the entire project
This ensures all committed code meets quality standards.
All API endpoints extend the abstract ApiEndpoint class (lib/ApiEndpoint.ts), which provides:
- Method validation (GET only)
- Token-based authentication via
x-api-tokenheader - Query parameter validation
- Centralized error handling
- Consistent response format
Each endpoint must implement:
getRequiredParams(): Returns array of required query parameter namesprocess(request): Contains endpoint-specific logic, returns the response data
Example endpoint structure:
class MyEndpoint extends ApiEndpoint {
private service: MyService;
constructor() {
super();
this.service = new MyService();
}
protected getRequiredParams(): string[] {
return ['param1', 'param2']; // or [] if no params required
}
protected async process(request: VercelRequest): Promise<MyOutput> {
const { param1, param2 } = request.query;
const data = await this.service.fetchData(param1, param2);
return { formatted: data };
}
}
const endpoint = new MyEndpoint();
export default async function handler(request: VercelRequest, response: VercelResponse) {
return endpoint.handle(request, response);
}External API calls are encapsulated in service classes (services/):
OpenWeatherMapService: Handles OpenWeatherMap 3.0 One Call APIGarminService: Handles Garmin aviation data API
Services:
- Read API keys from environment variables in constructor
- Throw errors if required environment variables are missing
- Handle URL construction and fetch logic
- Return typed responses
Full TypeScript coverage with models organized by domain:
models/common/: Shared types (ValidationError)models/weather/: Weather-specific types (WeatherResponse, WeatherOutput)models/metar/: METAR-specific types (MetarResponse, MetarOutput, etc.)
Output types (e.g., WeatherOutput) define the final API response format. Response types (e.g., WeatherResponse) define the external API response structure.
- Minimum thresholds: 80% lines/functions, 75% branches, 80% statements
- Current coverage: 97%+ across all metrics
- Tests are enforced via pre-commit hooks - only tested code can be committed
Tests are located in __tests__/ mirroring the source structure:
__tests__/
lib/ # Tests for lib/ utilities
services/ # Tests for services/ with mocked fetch
api/ # Integration tests for endpoints
Testing patterns:
- Pure functions (lib/middleware.ts): Test with vi.stubEnv() for environment variables
- Services: Mock fetch with vi.stubGlobal('fetch', mockFn)
- ApiEndpoint subclasses: Create mock subclass to test base class behavior
- Endpoints: Mock the service layer, focus on data transformation
Mock setup example:
// Use vi.hoisted to avoid initialization issues
const { mockFn } = vi.hoisted(() => ({
mockFn: vi.fn(),
}));
vi.mock('../../services/MyService', () => ({
MyService: vi.fn().mockImplementation(() => ({
myMethod: mockFn,
})),
}));- ESLint: TypeScript-specific rules with type-checked linting
- Prettier: Single quotes, 100 char line width, trailing commas
- TypeScript: Strict mode enabled, no implicit any
- Pre-commit hooks: Automated formatting and linting on git commit
Run npm run check before pushing to ensure all quality checks pass.
-
Create service (if needed) in
services/:- Accept required config in constructor
- Read environment variables for API keys
- Implement typed fetch methods
-
Define models in
models/[category]/:- Create response type for external API structure
- Create output type for your endpoint's response format
-
Create endpoint in
api/[name].ts:- Extend
ApiEndpoint - Inject service via constructor
- Implement
getRequiredParams()andprocess() - Export default Vercel handler function
- Extend
-
Write tests in
__tests__/api/[name].test.ts:- Test successful requests with mocked service
- Test validation errors (missing params, invalid method, auth)
- Test error handling (service failures)
- Aim for 80%+ coverage
-
Update environment:
- Add required env vars to
.env.example - Document in README.md
- Add required env vars to
-
Run quality checks:
npm run check # Ensures tests pass and code quality standards met
- Endpoint logic is in the
process()method - Required parameters are defined in
getRequiredParams() - External API changes require updating service classes and response types
- Output format changes require updating output types
- Always update tests when modifying functionality
- Run
npm run test:watchduring development for instant feedback
Required environment variables (see .env.example):
OPENWEATHERMAP_API_KEY: OpenWeatherMap API key (requires One Call API 3.0 subscription)API_TOKEN: Secure token for endpoint authentication (validate via x-api-token header)
Setup for local development:
cp .env.example .env.local
# Edit .env.local with actual valuesAfter any code addition or modification, always do the following automatically — do not ask, do not skip:
- Run
npm run formatto auto-fix formatting - Run
npm run checkto verify formatting, linting, type-checking, and tests all pass- If linting or type errors remain, fix them before proceeding
- Update
README.md— add or revise documentation for any new or changed endpoints, parameters, or response fields - Update
.claude/skills/*/SKILL.md— if the change affects an endpoint used by a skill, update the skill's description or output format accordingly
- API response fields (output interfaces —
*Output.tsfiles): use snake_case for all multi-word property names (e.g.,start_time,wind_speed,is_daytime) - TypeScript internal code (variables, function parameters, class properties, method names): use standard camelCase
- External API response types (
*Response.tsfiles that mirror a third-party API): mirror the naming of that API as-is, do not rename to match our convention - When adding new output fields, always use snake_case regardless of what the upstream API uses
- No abbreviations: always use full, descriptive names —
requestnotreq,responsenotres,latitudenotlat,longitudenotlon,parameternotparam, etc.
- No build step: Vercel compiles TypeScript automatically during dev and deployment
- Stateless endpoints: Each endpoint class instance is created per request export
- Single responsibility: Endpoints only transform data; services handle external communication
- Token authentication: Simple token-based auth via headers (not OAuth/JWT)
- Node 24+ required: Uses modern Node.js features and native fetch API