How Do You Avoid Hard-Coding API URLs in Angular?
1. What Does Hard-Coding an API URL Mean?
Hard-coding means directly writing the ASP.NET Core API address inside your Angular service:
private apiUrl = 'https://localhost:7001/api/products';
This is problematic because the API URL normally changes between environments:
Development → https://localhost:7001/api
UAT → https://uat-api.jntech.in/api
Production → https://api.jntech.in/api
A common solution is to keep the base API URL in environment-specific configuration and reference it from your services.
2. Full Simple Sample Program
Project Structure
src/
├── app/
│ ├── models/
│ │ └── product.ts
│ └── services/
│ └── product.ts
│
└── environments/
├── environment.ts
└── environment.production.ts
environment.ts — Development
export const environment = {
production: false,
apiUrl: 'https://localhost:7001/api'
};
environment.production.ts — Production
export const environment = {
production: true,
apiUrl: 'https://api.jntech.in/api'
};
models/product.ts
export interface Product {
id: number;
name: string;
price: number;
}
services/product.ts
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment';
import { Product } from '../models/product';
@Injectable({
providedIn: 'root'
})
export class ProductService {
private http = inject(HttpClient);
private apiUrl = `${environment.apiUrl}/products`;
getAll(): Observable<Product[]> {
return this.http.get<Product[]>(this.apiUrl);
}
getById(id: number): Observable<Product> {
return this.http.get<Product>(`${this.apiUrl}/${id}`);
}
create(product: Product): Observable<Product> {
return this.http.post<Product>(this.apiUrl, product);
}
update(id: number, product: Product): Observable<void> {
return this.http.put<void>(
`${this.apiUrl}/${id}`,
product
);
}
delete(id: number): Observable<void> {
return this.http.delete<void>(
`${this.apiUrl}/${id}`
);
}
}
3. What Did We Avoid?
Without environment configuration:
private apiUrl =
'https://localhost:7001/api/products';
The service itself knows about the server address.
That's undesirable.
Instead:
private apiUrl =
`${environment.apiUrl}/products`;
Now the responsibility is separated:
environment.ts
↓
Base API URL
↓
ProductService
↓
/products
4. How Does It Work?
The service imports:
import { environment }
from '../../environments/environment';
Then:
private apiUrl =
`${environment.apiUrl}/products`;
During development:
environment.apiUrl
↓
https://localhost:7001/api
+
/products
↓
https://localhost:7001/api/products
In production:
environment.apiUrl
↓
https://api.jntech.in/api
+
/products
↓
https://api.jntech.in/api/products
The ProductService itself doesn't have to change.
5. Using Multiple Services
This becomes particularly useful when your application has many services.
Product Service
private apiUrl =
`${environment.apiUrl}/products`;
Customer Service
private apiUrl =
`${environment.apiUrl}/customers`;
Order Service
private apiUrl =
`${environment.apiUrl}/orders`;
All of them share:
environment.apiUrl
So instead of repeating:
https://localhost:7001/api
throughout the application, it exists in one configuration location.
environment.apiUrl
│
┌──────────────┼──────────────┐
↓ ↓ ↓
ProductService CustomerService OrderService
↓ ↓ ↓
/products /customers /orders
6. Different Environments
You could have:
environments/
├── environment.ts
├── environment.sit.ts
├── environment.uat.ts
└── environment.production.ts
Development
export const environment = {
apiUrl: 'https://localhost:7001/api'
};
SIT
export const environment = {
apiUrl: 'https://sit-api.jntech.in/api'
};
UAT
export const environment = {
apiUrl: 'https://uat-api.jntech.in/api'
};
Production
export const environment = {
apiUrl: 'https://api.jntech.in/api'
};
Then you can build using:
ng build --configuration sit
ng build --configuration uat
ng build --configuration production
7. Configure File Replacement
For example, angular.json can contain:
"configurations": {
"uat": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.uat.ts"
}
]
},
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.production.ts"
}
]
}
}
Your services still always import:
import { environment }
from '../../environments/environment';
Angular's build configuration handles the replacement.
8. Better Organization for Larger Applications
For a larger Angular application, you can also centralize endpoint paths.
For example:
export const environment = {
apiUrl: 'https://localhost:7001/api'
};
Then create:
api-endpoints.ts
import { environment } from '../../environments/environment';
export const API_ENDPOINTS = {
products: `${environment.apiUrl}/products`,
customers: `${environment.apiUrl}/customers`,
orders: `${environment.apiUrl}/orders`
};
Then:
import { API_ENDPOINTS } from '../config/api-endpoints';
private apiUrl = API_ENDPOINTS.products;
This produces:
environment.ts
↓
Base API URL
↓
api-endpoints.ts
↓
┌────────┬─────────┬────────┐
↓ ↓ ↓
Products Customers Orders
This can be useful when an application contains many endpoints.
9. Another Option: Use a Relative API URL
There is another useful deployment architecture.
Suppose Angular and ASP.NET Core are exposed through the same domain:
https://www.jntech.in
Angular:
https://www.jntech.in/
API:
https://www.jntech.in/api/products
Then Angular may use a relative URL:
private apiUrl = '/api/products';
The browser automatically calls the current host:
https://www.jntech.in/api/products
This can eliminate the need to configure the hostname in Angular.
It is particularly useful when a reverse proxy/API gateway exposes the frontend and API under the same origin.
10. Build-Time vs Runtime Configuration
Environment files solve hard-coding, but they are primarily a build-time configuration mechanism.
For example:
ng build --configuration uat
produces a build containing the UAT configuration.
ng build --configuration production
produces a build containing the production configuration.
Therefore:
Source
├── UAT Build → UAT API URL
│
└── Prod Build → Production API URL
If your deployment requirement is:
Build Angular once
↓
Create one artifact
↓
Deploy to SIT
↓
Promote same artifact to UAT
↓
Promote same artifact to Production
then consider runtime configuration instead.
For example:
Angular starts
↓
Loads config.json
↓
Reads apiUrl
↓
Calls ASP.NET Core
This allows the deployment environment to supply the URL rather than compiling it into Angular.
11. Don't Confuse Configuration with Security
Moving this:
'https://api.jntech.in/api'
from a service into:
environment.apiUrl
improves configuration management.
It does not make the URL secret.
Angular executes in the browser, so users can inspect the API requests.
Therefore:
Fine in Angular
export const environment = {
apiUrl: 'https://api.jntech.in/api'
};
Don't put in Angular
export const environment = {
sqlPassword: 'Password123',
jwtSigningKey: 'secret',
privateApiKey: 'secret'
};
Secrets belong on the backend.
Key Points
- Don't hard-code the ASP.NET Core API base URL in every Angular service.
- Centralize the base URL in environment-specific configuration:
environment.apiUrl
- Build endpoint URLs like:
`${environment.apiUrl}/products`
- Different environments can have different URLs:
DEV → localhost
SIT → SIT API
UAT → UAT API
PROD → Production API
fileReplacementscan select the appropriate environment file during the build.- For larger applications, endpoint paths can also be centralized.
- If Angular and the API share the same origin, relative URLs such as
/api/productscan simplify configuration. - For build once, deploy everywhere, runtime configuration is usually more appropriate than build-time environment files.
- Never store secrets in Angular configuration.
Interview Questions and Answers
1. How do you avoid hard-coding API URLs in Angular?
Store the base API URL in centralized environment-specific configuration and reference it from Angular services.
private apiUrl =
`${environment.apiUrl}/products`;
2. Why shouldn't API URLs be hard-coded?
Because API addresses commonly differ between Development, SIT, UAT, and Production. Hard-coding makes deployments and maintenance more difficult.
3. Where can the API base URL be stored?
For build-time configuration:
export const environment = {
apiUrl: 'https://localhost:7001/api'
};
Then access it using:
environment.apiUrl
4. Should every Angular service contain the complete server URL?
Prefer not to repeat it.
Instead of:
'https://localhost:7001/api/products'
use:
`${environment.apiUrl}/products`
This centralizes the base URL.
5. Can Angular use relative API URLs?
Yes. If the frontend and backend are exposed through the same origin, you can use:
'/api/products'
instead of hard-coding the hostname.
6. What if Angular communicates with multiple APIs?
Define separate base URLs:
export const environment = {
productApiUrl: 'https://product-api.example.com/api',
orderApiUrl: 'https://order-api.example.com/api'
};
Each service can then use its corresponding base URL.
7. Are environment files the only solution?
No. Common approaches include:
- Environment/build configuration — simple and suitable when separate builds per environment are acceptable.
- Runtime configuration — useful when the same build artifact must be promoted across environments.
- Relative URLs — useful when frontend and API are exposed under the same origin.
8. Give a short interview answer.
I avoid hard-coding API URLs by centralizing the API base URL in Angular configuration and referencing it from services. For environment-specific builds, I can use Angular build configurations and file replacements. If the same build must be promoted through SIT, UAT, and Production, I prefer runtime configuration or relative URLs where the deployment architecture allows it.