Aspida
Aspida is a TypeScript-friendly HTTP client wrapper designed for both browser and Node.js environments. It streamlines API client generation by enabling developers to define API endpoint types through a convention-over-configuration approach, leveraging a directory structure and `DefineMethods` type aliases. This approach significantly enhances type safety for API interactions at compile time, eliminating the need for manual client creation. Aspida supports integration with popular HTTP clients like Axios, Fetch, and Node-Fetch via official adapter packages. The current stable version is 1.14.0, and the project demonstrates an active release cadence with frequent updates. Its key differentiators include robust type inference for paths, query parameters, headers, request bodies, and responses, comprehensive support for `FormData` and `URLSearchParams`, and a unique workflow that generates a fully type-safe client based on a filesystem-defined API structure.
Common errors
-
Cannot find module '../api/$api' or its corresponding type declarations.
cause The Aspida API client definition file `$api.ts` has not been generated, or the import path is incorrect.fixEnsure you have `"api:build": "aspida"` in your `package.json` scripts, then run `npm run api:build`. Verify the import path `../api/$api` matches the location where `aspida` generates the file. -
TypeError: api is not a function
cause The `api` object imported from the generated `$api.ts` is not being called as a factory function with an Aspida adapter, or the adapter itself is missing/incorrect.fixEnsure you are initializing the client correctly with `const client = api(aspida());`. Make sure `@aspida/axios` (or your chosen adapter) is installed and imported as `aspida`. -
Property 'v1' does not exist on type '(...)'
cause This typically means there's a mismatch between the expected API structure in your code and what was generated by Aspida, or the generated `$api.ts` file is outdated.fixDouble-check your `api` directory structure against how you are accessing the client (e.g., `client.v1.users`). Re-run `npm run api:build` to ensure the client reflects the latest directory definitions.
Warnings
- breaking Aspida moved the core `aspida` package to a peerDependency in v1.14.0. This means you must explicitly install `aspida` alongside any `@aspida/*` adapter packages. Previously, it might have been implicitly installed.
- breaking Starting with v1.14.0, import names for generated client files changed their postfix to a hash (`#`). This could affect existing import statements that relied on the previous naming convention for generated type definition files.
- gotcha The Aspida CLI requires a build step (`npm run api:build`) to generate the `$api.ts` type definition file based on your `api` directory structure. Forgetting this step or not having it configured will result in missing module errors.
- gotcha When defining path variables, specifying the type (e.g., `_userId@number`) is crucial. If omitted (e.g., `_userId/`), the path variable type defaults to `number | string`, which might be less specific than intended and lead to weaker type checking.
Install
-
npm install aspida -
yarn add aspida -
pnpm add aspida
Imports
- DefineMethods
const { DefineMethods } = require('aspida');import type { DefineMethods } from 'aspida'; - api
const api = require('../api/$api');import api from '../api/$api';
- aspida
const aspida = require('@aspida/axios');import aspida from '@aspida/axios';
Quickstart
import aspida from '@aspida/axios';
import api from '../api/$api'; // This file is generated by 'npm run api:build'
// Ensure this directory structure is created and 'aspida' command is run:
// api/
// ├── v1/
// │ ├── users/
// │ │ ├── index.ts
// │ │ └── _userId@number/
// │ │ └── index.ts
// package.json: { "scripts": { "api:build": "aspida" } }
// Example: api/v1/users/index.ts
// import type { DefineMethods } from "aspida";
// type User = { id: number; name: string; };
// export type Methods = DefineMethods<{
// get: { query?: { limit: number; }; resBody: User[]; };
// post: { reqBody: { name: string; }; resBody: User; };
// }>;
(async () => {
const userId = 0;
const limit = 10;
const client = api(aspida()); // Initialize the client with the Axios adapter
try {
// Example: POST /v1/users
const newUser = await client.v1.users.post({ body: { name: 'aspida-user' } });
console.log('Created user:', newUser.body);
// Example: GET /v1/users?limit=10
const users = await client.v1.users.get({ query: { limit } });
console.log('Users list:', users.body);
// Example: GET /v1/users/0
const userById = await client.v1.users._userId(userId).$get();
console.log('User by ID:', userById.body);
} catch (error) {
console.error('API request failed:', error);
}
})();