Skip to main content

Migrating from Axios

@data-client/rest replaces axios with a declarative, type-safe approach to REST APIs.

AI-assisted migration

Install the REST setup skill to automate the migration with your AI coding assistant. It auto-detects axios in your project and runs the codemod for deterministic transforms, then guides you through the manual steps that require judgment (interceptors, error handling, schema definitions, etc.).

npx skills add reactive/data-client --skill data-client-schema --skill data-client-rest-setup --skill data-client-rest

Then run skill /data-client-rest-setup to start the migration. It will detect axios and apply the appropriate migration sub-procedure automatically.

Why migrate?

Type-safe paths

With axios, API paths are opaque strings — typos and missing parameters are only caught at runtime:

// axios: no type checking — typo silently produces wrong URL
axios.get(`/users/${usrId}`);

With RestEndpoint, path parameters are inferred from the path template and enforced at compile time:

const getUser = new RestEndpoint({ path: '/users/:id', schema: User });
// TypeScript enforces { id: string } — typos are compile errors
getUser({ id: '1' });

This also means IDE autocomplete works for every path parameter.

Additional benefits

  • Normalized cache — shared entities are deduplicated and updated everywhere automatically
  • Declarative data dependencies — components declare what data they need via useSuspense(), not how to fetch it
  • Optimistic updates — instant UI feedback before the server responds
  • Zero boilerplateresource() generates a full CRUD API from a path and schema

Quick reference

Axios@data-client/rest
baseURLurlPrefix
headers configgetHeaders()
interceptors.requestgetRequestInit() / getHeaders()
interceptors.responseparseResponse() / process()
timeoutAbortSignal.timeout() via signal
params / paramsSerializersearchParams / searchToString()
cancelToken / signalsignal (AbortController)
responseType: 'blob'Custom parseResponse() — see file download
auth: { username, password }getHeaders() with btoa()
transformRequestgetRequestInit()
transformResponseprocess()
validateStatusCustom fetchResponse()
onUploadProgressCustom fetchResponse() with ReadableStream
isAxiosError / error.responseNetworkError with .status and .response

Migration examples

Basic GET

api.ts
import axios from 'axios';

export const getUser = (id: string) =>
axios.get(`https://api.example.com/users/${id}`);
usage.ts
const { data } = await getUser('1');

Instance with base URL and headers

api.ts
import axios from 'axios';

const api = axios.create({
baseURL: 'https://api.example.com',
headers: { 'X-API-Key': 'my-key' },
});

export const getPost = (id: string) => api.get(`/posts/${id}`);
export const createPost = (data: any) => api.post('/posts', data);

POST mutation

api.ts
import axios from 'axios';

const api = axios.create({ baseURL: 'https://api.example.com' });

export const createPost = (data: { title: string; body: string }) =>
api.post('/posts', data);

Interceptors → lifecycle methods

Axios interceptors map to RestEndpoint lifecycle methods:

api.ts
import axios from 'axios';

const api = axios.create({ baseURL: 'https://api.example.com' });

// Request interceptor — add auth token
api.interceptors.request.use(config => {
config.headers.Authorization = `Bearer ${getToken()}`;
return config;
});

// Response interceptor — unwrap .data
api.interceptors.response.use(
response => response.data,
error => Promise.reject(error),
);
tip

RestEndpoint already returns parsed JSON by default — no interceptor needed to unwrap response.data.

Error handling

import axios from 'axios';

try {
const { data } = await axios.get('/users/1');
} catch (err) {
if (axios.isAxiosError(err)) {
console.log(err.response?.status);
console.log(err.response?.data);
}
}

Cancellation

import axios from 'axios';

const controller = new AbortController();
axios.get('/users', { signal: controller.signal });
controller.abort();

See the abort guide for more patterns.

Timeout

Before (axios)
axios.get('/users', { timeout: 5000 });
After (data-client)
const getUsers = new RestEndpoint({
path: '/users',
signal: AbortSignal.timeout(5000),
});

Codemod

For non-AI workflows, a standalone jscodeshift codemod handles the mechanical parts of migration. (The AI skill above runs this automatically as its first step.)

npx jscodeshift -t https://dataclient.io/codemods/axios-to-rest.js --extensions=ts,tsx,js,jsx src/

The codemod automatically:

  • Replaces import axios from 'axios' with import { RestEndpoint } from '@data-client/rest'
  • Converts axios.create({ baseURL, headers }) into a base RestEndpoint class
  • Transforms axios.get(), .post(), .put(), .patch(), .delete() into RestEndpoint instances

After running the codemod, you'll need to manually:

  • Define Entity schemas for your response data
  • Convert imperative api.get() call sites to declarative useSuspense() hooks
  • Migrate interceptors to lifecycle methods (see examples above)
  • Set up resource() for CRUD endpoints