Mock Server
The MockServer service is an in-process HTTP mock server that intercepts Angular's HTTP client requests and returns handler-defined responses. No separate server process required.
Overview
MockServer replaces Angular's HttpBackend with a custom implementation that matches incoming requests against registered handlers. Requests that match a handler are intercepted and answered locally. Requests that don't match are forwarded to the real network via FetchBackend.
- Runs Inside the App
- Runs entirely inside the Angular application. No separate server process, service worker, or proxy configuration needed. It relies on Angular's
Routerto parse and match request URLs. - Route-Aware Path Matching
- Uses Angular Router's
defaultUrlMatcherso path parameters like:idand the**wildcard work the same way as they do in your route definitions. Paths match in full: a handler for/usersdoes not answer/users/4. When two registered paths can match the same URL, such as/users/meand/users/:id, the one registered first wins, so register specific paths before parameterized ones. - Async Handler Support
- Handlers can be synchronous or async. Use
await c.delay(ms)to simulate network latency inside an async handler, or rely on the globaldelayoption for a uniform delay on all synchronous responses. - Selective Passthrough
- A request is handled only when its URL starts with the configured
baseURL. Relative and root-relative URLs (such as asset requests foricons/custom-icons.svg), other origins, unmatched paths, and unregistered HTTP methods all go to the real network.
Provider Setup
Add provideMockServer to the providers array of your application configuration, after provideHttpClient. Keep it behind a feature flag or environment check so it is only active during development:
import { provideMockServer } from '@/app/core/mock-server';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(),
...(isDevMode()
? [
provideMockServer({
baseURL: 'https://api.example.com',
delay: 400,
logging: {
bypass: false,
handler: true,
},
}),
]
: []),
],
};Configuration
All options are required.
| Option | Type | Description |
|---|---|---|
baseURL | string | The absolute URL that the mock server handles (e.g. 'https://api.example.com'). provideMockServer throws when it does not start with http. Only requests whose URL starts with this value are considered. When it contains a path, such as 'https://example.com/api', handler paths include those segments: /api/users. Write it without a trailing slash, otherwise a request to the base URL itself ('https://api.example.com') is not handled. |
delay | number | Global delay in milliseconds applied only to handlers that return synchronously. Handlers that return a Promise are never delayed globally and manage their own delay via c.delay(). |
logging.bypass | boolean | Log requests that were not matched and passed through to the network. |
logging.handler | boolean | Log requests that were handled by the mock server, including the response. |
Registering Handlers
Inject MockServer and call the method that matches the HTTP verb you want to intercept: delete, get, head, options, patch, post, or put. Handlers are typically registered in a service:
@Service()
export class ApiMockService {
private mockServer = inject(MockServer);
constructor() {
this.mockServer.get('/users', (c) => c.json([{ id: 1, name: 'Alice' }]));
this.mockServer.post<unknown, { name: string }>('/users', (c) =>
c.json({ ...c.req.body, id: 2 })
);
this.mockServer.delete('/users/:id', (c) => c.json(null, { status: 204 }));
}
} The handlers are registered when the service is created. Inject it from an app initializer so its constructor runs at startup, and keep the initializer inside the same condition as provideMockServer, since MockServer cannot be created without its configuration:
provideAppInitializer(() => {
inject(ApiMockService);
}), Leading and trailing slashes are optional in both registered paths and request URLs, so /users, users, and /users/ refer to the same path. Use / to handle the base URL itself. The ** wildcard matches any remaining segments: /files/** answers /files, /files/a, and /files/a/b. Registering the same path and method again replaces the earlier handler.
Handler Context
Every handler receives a single context argument that exposes the incoming request and a set of response helpers:
| Property | Type | Description |
|---|---|---|
c.req.body | BodyType | null | The request body as passed to the HttpClient call. |
c.req.params | ParamsType | Path parameters extracted from the URL (e.g. { id: '4' } for a path /users/:id). Typed automatically from the path string when no type arguments are passed. |
c.req.queryParams | HttpParams | Query string parameters. When the Angular HttpClient call supplies HttpParams, those take precedence over the raw URL query string. |
c.req.raw | HttpRequest<unknown> | The original Angular HTTP request object. |
c.json(body, options?) | Response | Returns a JSON response with Content-Type: application/json. Default status is 200. |
c.text(body, options?) | Response | Returns a plain-text response with Content-Type: text/plain. Default status is 200. |
c.blob(body, options?) | Response | Returns a binary response. The Content-Type is derived from the Blob.type, falling back to application/octet-stream when the type is empty. |
c.delay(ms) | Promise<void> | Awaitable helper that pauses the handler for the specified number of milliseconds. Use inside async handlers to simulate network latency. |
Every register method accepts three optional type arguments: ParamsType, BodyType, and ResponseType. BodyType types c.req.body and ResponseType types the body passed to c.json(). TypeScript cannot infer some type arguments while others are given, so passing any of them turns off path parameter inference and c.req.params no longer exposes the parameters in the path. Pass ParamsType as well when such a handler reads path parameters.
Usage Examples
GET with Path Parameters
mockServer.get('/users/:id', (c) => {
const { id } = c.req.params; // typed as string
return c.json({ id, name: 'Alice' });
});POST with a Request Body
mockServer.post<unknown, { name: string }>('/users', (c) => {
const name = c.req.body?.name ?? '';
return c.json({ id: 42, name }, { status: 201 });
});Query Parameters
mockServer.get('/users', (c) => {
const page = c.req.queryParams.get('page') ?? '1';
const limit = c.req.queryParams.get('limit') ?? '10';
return c.json({ page, limit, items: [] });
});Custom Status Code and Headers
mockServer.head('/users', (c) =>
c.json(null, {
headers: { 'Content-Length': '1024' },
})
);
mockServer.options('/users', (c) =>
c.json(null, {
headers: {
Allow: 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
},
})
);Error Responses
Return any status code outside the 2xx range and the handler will emit an HttpErrorResponse to the caller, exactly like a real server error would.
mockServer.get('/users/:id', (c) => {
const { id } = c.req.params;
if (id === '0') {
return c.json({ message: 'Not found' }, { status: 404 });
}
return c.json({ id, name: 'Alice' });
}); A handler that throws, or returns a Promise that rejects, also emits an HttpErrorResponse. It carries the thrown value as error and a status of 0.
Simulated Delay
mockServer.get('/users', async (c) => {
await c.delay(1200); // simulate a slow network
return c.json([{ id: 1, name: 'Alice' }]);
});Blob / File Download
mockServer.get('/files/:name', (c) => {
const content = new Blob(['hello, world'], { type: 'text/plain' });
return c.blob(content);
});How It Works
provideMockServerreplaces Angular'sHttpBackendwithMockServerBackend.- On every outgoing request,
MockServerBackendasksMockServerwhether the URL starts withbaseURLand a handler exists for its path and method. - If a match is found, the handler runs and returns an
Observable<HttpEvent>that is indistinguishable from a real HTTP response as far as the Angular HTTP client is concerned. - If no match is found, the request is forwarded to the underlying
FetchBackendso real network calls continue to work normally.
Caveats
Intercepted requests never leave Angular, so they will not appear in the browser's Network tab. The mock server short-circuits the request entirely before it reaches the browser's fetch infrastructure. Use the built-in logging.handler option to inspect intercepted traffic in the browser console instead.