Stop repeating HttpClient boilerplate in Angular
DEV Community

Stop repeating HttpClient boilerplate in Angular

Set your API base URL and headers once with provideApi(), then inject(ApiService) anywhere. A tiny typed wrapper for Angular 17+. Every Angular app that talks to a REST API ends up with the same few lines in every service: read the base URL from the environment, build the headers, glue the URL together, call HttpClient . Then the app grows, a second backend shows up, and those lines multiply. I got tired of writing them, so years ago I wrote a small wrapper for my own apps. I just released version 3.0 of it, rebuilt for modern Angular: @arxis/api . The problem This is what a typical service looks like with HttpClient : @Injectable({ providedIn: 'root' }) export class UserService { private readonly http = inject(HttpClient); private readonly baseUrl = environment.apiUrl; private readonly headers = new HttpHeaders({ 'X-Api-Key': environment.apiKey }); getUsers() { return this.http.get (${this.baseUrl}/users, { headers: this.headers }); } createUser(body: CreateUserDto) { return this.http.post (${this.baseUrl}/users, body, { headers: this.headers }); } } Only one part of each method is about users: 'users' . Everything else is plumbing, repeated in every service. Configure the API once npm install @arxis/api Tell the app where your API lives and which headers go with every request: // app.config.ts import { provideHttpClient } from '@angular/common/http'; import { provideApi } from '@arxis/api'; export const appConfig: ApplicationConfig = { providers: [ provideHttpClient(), provideApi({ url: environment.apiUrl, globalHeaders: { 'X-Api-Key': environment.apiKey }, }), ], }; Now each service only describes its endpoints: import { ApiService } from '@arxis/api'; @Injectable({ providedIn: 'root' }) export class UserService { private readonly api = inject(ApiService); getUsers() { return this.api.get ('users'); } createUser(body: CreateUserDto) { return this.api.post ('users', body); } } ApiService has get , post , put , patch and delete . They're typed, they accept query params as a plain object, and they have the same observe: 'response' and observe: 'events' overloads as HttpClient : api.get ('users', { role: 'admin' }); // GET /users?role=admin api.get ('users', null, { observe: 'response' }); // full HttpResponse api.post('upload', formData, { observe: 'events', reportProgress: true }); It's still your HttpClient @arxis/api doesn't create its own HTTP stack. It uses the HttpClient you configure, so everything you already have keeps working: interceptors, withFetch() , server-side rendering and HttpTestingController . For example, an auth token that changes while the app runs belongs in an interceptor, and it applies to every ApiService request: export const authInterceptor: HttpInterceptorFn = (req, next) => { const token = inject(AuthService).token(); return next(token ? req.clone({ setHeaders: { Authorization: Bearer ${token} } }) : req); }; // app.config.ts provideHttpClient(withInterceptors([authInterceptor])), provideApi({ url: environment.apiUrl }), More than one backend Extend ApiService once per API and pass its config to super() : @Injectable({ providedIn: 'root' }) export class BillingApiService extends ApiService { constructor() { super({ url: environment.billingUrl }); } } Or give a lazy-loaded part of the app its own base URL: { path: 'admin', providers: [provideApi({ url: environment.adminApiUrl })], loadChildren: () => import('./admin/admin.routes'), } Testing Since requests go through Angular's HttpClient , you test your services the usual way: TestBed.configureTestingModule({ providers: [ provideHttpClient(), provideHttpClientTesting(), provideApi({ url: 'https://api.example.com' }), ], }); TestBed.inject(UserService).getUsers().subscribe(); TestBed.inject(HttpTestingController).expectOne('https://api.example.com/users').flush([]); What's new in 3.0 - Works with Angular 17 through 22. - ApiService isprovidedIn: 'root' and usesinject() . - provideApi() returnsEnvironmentProviders , likeprovideHttpClient() andprovideRouter() . - If you forget provideApi() , the error tells you what to add. - About 1 KB minified and gzipped. Coming from 1.x? The migration guide has the six steps. Try it - Live example: open it in StackBlitz - GitHub: renearias/arxis - npm: @arxis/api If it saves you some boilerplate, a star on GitHub helps other Angular developers find it. Issues and ideas are welcome too. Top comments (0)

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.