How One Axios Request Moves from Call to Response
Axios’s most useful behavior is easy to miss because its public call is so small. This chapter follows one Axios request from a URL and configuration object to a resolved response or a classified failure. The goal is practical: once the stages are clear, a surprising header, body, status, or error can be traced to the stage that owns it instead of being blamed on “the network.”
Consider a call such as:
const response = await axios.get('/users', {
params: { page: 2 },
timeout: 5000,
});
The call does not immediately open a connection. Axios first turns the caller’s intent into a prepared request. It merges policy, validates some options, chooses a method, builds headers, runs request-side hooks and transformations, selects a transport, and only then crosses the network boundary. When the transport returns, the path reverses: Axios classifies the status, transforms the response, runs response hooks, and exposes either a response or an error.
Before the adapter: from caller input to prepared request
The callable instance supplies context
The default Axios export is a callable instance backed by the Axios class. That design lets axios(config) and convenience methods such as axios.get(url, config) share one request implementation. A custom instance created with axios.create() uses the same machinery but carries its own defaults and interceptors.
The first transition is therefore from a public method call to a normalized request description. If the caller passes a string, Axios places that string into the configuration as the URL. If the caller passes an object, it uses that object as the request configuration. An alias such as get supplies the method and URL for the caller, so the application does not need to repeat them in the config.
This is more than syntactic cleanup. The callable instance is where destination-specific context can live. An API client may have a base URL, a timeout, default headers, and request hooks. Those values are available to the request without changing the global Axios client. The request itself can still override a value for one operation.
Configuration is merged by meaning
Axios combines the instance defaults with the request configuration, but it does not use one universal rule for every property. Request-specific values such as the URL, method, and body come from the request layer. Defaultable options such as timeout, response type, adapter, and progress handlers can be inherited from the instance. Headers receive a deeper, case-insensitive merge.
That distinction affects the lifecycle. The body in the example belongs to this request; Axios does not silently combine it with a body stored in a global or instance default. A timeout, by contrast, can be an instance policy that applies to every request unless one call overrides it. The merged result is a new configuration, not an instruction to mutate the application’s original object into a permanent global state.
The merge also preserves supported custom information, including own enumerable symbol-keyed options. That is useful for an application-specific interceptor or adapter flag, but it does not mean inherited properties are trusted. The current implementation takes care to avoid using values supplied only through a polluted Object.prototype. This defensive behavior matters because a configuration property can change execution, not merely describe it.
After merging, Axios validates selected option groups. Transitional settings must have the expected kinds of values, and a serializer can be either a function or an object with supported functions. Misspelled options can produce warnings rather than changing behavior silently. The purpose of this stage is not to validate the remote server’s API; it is to reject or flag configuration that Axios itself cannot interpret reliably.
Method and headers are made concrete
Axios resolves the method to a lowercase value, using the request method, the instance default, or get when none is supplied. It then combines common headers with headers for that method and converts the result to AxiosHeaders.
AxiosHeaders is an explicit representation rather than a plain object passed unchanged to every transport. Header names are matched case-insensitively, values are normalized, and the final representation can be rendered for XHR, Node HTTP, or Fetch. It also supports controlled overwrites. A header value can be intentionally omitted, and an internal false value can prevent a later default from being installed unless a caller explicitly rewrites it.
This stage is where a request’s shared policy becomes wire metadata. A default Accept header may survive from the client. A method-specific content type may be selected. A request interceptor can add a trace identifier or authorization value. Later transformations or the adapter may add information required by a particular body representation. Because headers are normalized around the lifecycle, a caller should not reason only from the spelling or shape of the object they first supplied.
Header preparation also illustrates why the stages have boundaries. Axios can ensure that matching header names do not create accidental duplicates and that ordinary header values do not contain forbidden line breaks. It cannot know whether sending a valid authorization header to a particular host is appropriate. Syntax and policy are related, but they are not the same decision.
Request interceptors run before dispatch
Once the request configuration has been prepared, Axios builds an interceptor chain. Request interceptors can inspect or modify the configuration before the transport is selected. They are commonly used for behavior that belongs to many requests from one client: attaching a current token, adding observability metadata, or rejecting a request that lacks required policy.
By default, request interceptors participate in an asynchronous Promise chain. They can also be marked synchronous when their work does not need asynchronous operations. A runWhen predicate can skip a request interceptor for a particular configuration. In the current legacy ordering mode, request interceptors execute in reverse registration order: the last one added runs first. Response interceptors use the opposite, registration order, but they have not run yet at this point.
The important lifecycle fact is that an interceptor can replace the configuration that moves forward. It can also throw, return a rejection, or alter headers and data. A synchronous rejection handler that returns normally may allow Axios to dispatch with the last valid configuration; a handler that throws or returns a rejected Promise prevents dispatch. These outcomes are different even though both originate in a request hook.
A request interceptor therefore acts like middleware with authority over the next state. That authority is useful, but it creates coupling. If a token is attached globally, every host reachable through that client may receive it. If validation is hidden in a hook, a caller reading only the request method may not see why the request is blocked. The lifecycle makes both the power and the ownership visible.
At and after the adapter: transport, settlement, and response
Cancellation is checked before transport
Before request data is dispatched, Axios checks for an already-requested cancellation through either an AbortSignal or the legacy CancelToken. If cancellation is already active, it raises a canceled error without making a real request. This early check is a meaningful transition: the request ends in a terminal state before it reaches an adapter.
If it is still active, the request transform runs. The default transform preserves binary and stream-like values, turns URLSearchParams into URL-encoded text, converts objects to JSON by default, and can build URL-encoded or multipart data when the content type calls for it. An HTML form can become FormData. The selected representation is now part of the adapter’s input; the adapter is not expected to guess what the original JavaScript object meant.
The response side has an analogous transformation later. A textual response can be parsed as JSON under Axios’s transitional parsing rules, and a caller can supply a reviver. An explicit JSON response type can request stricter parsing, while backward-compatible implicit parsing may return the raw text if parsing fails. That difference belongs to response transformation, not to the remote server and not to status settlement.
The adapter is the transport boundary
After the request-side work, Axios resolves an adapter. Its default priority list includes XHR, Node HTTP, and Fetch; the first adapter supported by the current build and environment is selected. The caller can choose one explicitly, provide an ordered list, or supply a custom adapter function.
The shared contract ends at a carefully defined boundary. A custom adapter receives the merged configuration after request interceptors and request transformations have run. It is responsible for making the request in its own way and returning a valid Axios response object. That response contains the data, status, status text, headers, and configuration needed by the rest of the Axios lifecycle. A custom adapter can use Axios’s settlement helper when it wants the standard rule that 2xx responses resolve and other statuses reject.
What the adapter does not receive is a promise that every built-in feature will appear automatically. The Node HTTP adapter can provide redirects, proxies, decompression, and Node streams. XHR uses browser events and browser credential rules. Fetch depends on the available Request, Response, and readable-stream implementations. A custom adapter must decide how to implement capabilities such as progress, cancellation, redirects, and response types. The shared response contract makes the adapter swappable; it does not make the underlying transport interchangeable in every detail.
The transport stage can fail in several ways. The adapter may reject before receiving a response because a connection cannot be made, a protocol is unsupported, a timeout fires, or a request body cannot be sent. It may receive a response whose status Axios considers unsuccessful. It may return a response whose body later fails during transformation. These cases all leave the happy path, but they do so at different boundaries and carry different evidence.
Status settlement is not response parsing
When an adapter has a response, Axios uses status policy to decide whether the response promise should resolve. The default validateStatus accepts statuses from 200 through 299. A caller can change that rule—for example, resolving a 404 so that an application can treat “not found” as an ordinary result, or rejecting anything at 500 and above while handling client errors in the normal response path.
This decision is not the same as parsing the response body. A server can return a valid JSON document with a status that Axios rejects by default. Conversely, a status accepted by a custom validator can still produce a transformation error if the body cannot be parsed as the requested type. Keeping those decisions separate prevents a common debugging mistake: treating every rejected promise as evidence that the network failed.
For built-in adapters, status settlement happens at the adapter boundary or through the standard settlement helper. Once the result comes back through dispatch, Axios checks cancellation again. This second check matters because cancellation can be requested while the transport is in flight or while a response is becoming available. If the response is still valid, Axios applies response transformations and wraps the headers in AxiosHeaders before returning it.
On a rejection that includes a response, Axios can still transform the response data and headers before passing the error onward. That lets callers inspect a structured error body without assuming that every rejected promise lacks a response. A rejection with no response instead points toward a setup, connection, timeout, cancellation, or other transport-side cause.
Response interceptors complete the reverse path
After dispatch resolves or rejects, response interceptors run. The first registered response interceptor runs first, and each receives the result of the preceding one. A response interceptor can unwrap response.data, normalize a server envelope, record timing information, or convert a known error response into an application-specific result. If a fulfillment handler throws, the next rejection handler in the Promise chain can catch it; later fulfillment handlers may run after that rejection has been handled.
This ordering is the reverse-side counterpart to request preparation. Request hooks shape what leaves the process; response hooks shape what the application receives. Neither hook is a sandbox. Both execute in the caller’s process and can read or change credentials, data, and control flow. Their position in the lifecycle explains why an interceptor is more powerful than a logging callback and why it should be scoped and removed when it is temporary.
The whole path is easier to remember as a sequence of ownership transitions:
The arrows are not merely a chronology. They show what is retained and what changes. The application’s intent becomes a merged configuration. Headers become a normalized transport representation. An object body may become bytes or a stream. The adapter adds runtime-specific request details and returns a response. Settlement adds status meaning. Response transformations change the data exposed to the caller, and response interceptors can change the final value again.
Finding the owner of a surprising result
Suppose the server reports that it received URL-encoded fields when the caller expected JSON. Inspect the request content type and request transform first. Suppose an authorization header appears on a request made through a client that serves multiple domains. Inspect instance and global defaults, then request interceptors, before blaming the adapter. Suppose a 404 enters catch with a response attached. Inspect validateStatus; the server may have responded normally, while Axios applied its default status policy. Suppose no response exists and the code is ECONNABORTED, ETIMEDOUT, or ERR_CANCELED. Inspect the request lifetime controls and the adapter’s environment rather than treating it as an HTTP application error.
The same reasoning works for custom adapters. If the adapter sees an untransformed object, the contract has been violated on the path before or inside the custom implementation. If response headers are ordinary objects after Axios completes, the caller may be looking before the response normalization stage or at a custom result that did not satisfy the response shape. If a custom adapter returns a status but never applies the intended settlement rule, the caller may receive behavior different from the built-in adapters.
Axios’s lifecycle is useful because it gives each class of behavior a home. Configuration establishes policy. Headers represent wire metadata. Interceptors add caller-controlled middleware. Transforms translate data. The adapter performs environment-specific transport. Settlement turns status into Promise control flow. Response transforms and interceptors shape what the application consumes. Once those boundaries are explicit, the next design question follows naturally: how should a team assign each policy to library defaults, a scoped instance, or one request without letting convenient configuration leak across destinations?