Reviews how an Angular app consumes its .NET backend's API contract. Flags hand-typed TypeScript interfaces re-declared per feature instead of generating a typed client from the backend's OpenAPI/Swagger spec (NSwag/openapi-typescript), a generated client checked in but never regenerated against the current API version, hand-written HttpClient calls duplicating what the generated client already provides, and no CI step failing the build when the frontend model drifts from the backend contract...
Scanned 9/6/2026
Install to Claude Code
npx -y skills add AgenticPawan/FullStack-Pilot --skill angular-api-client-codegen --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Angular Api Client Codegen?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/agenticpawan-angular-api-client-codegen)More formats (shields.io, HTML) on the badges page.
---
name: angular-api-client-codegen
description: Reviews how an Angular app consumes its .NET backend's API contract. Flags hand-typed TypeScript interfaces re-declared per feature instead of generating a typed client from the backend's OpenAPI/Swagger spec (NSwag/openapi-typescript), a generated client checked in but never regenerated against the current API version, hand-written HttpClient calls duplicating what the generated client already provides, and no CI step failing the build when the frontend model drifts from the backend contract. Outputs findings with pilot-angular api-client-codegen standard IDs.
when_to_use: NSwag, OpenAPI client, openapi-typescript, swagger codegen, generated API client, hand-typed interface drift, DTO drift frontend backend, nswag.json, OpenAPI spec generation, typed HTTP client generation
---
## Standard IDs
| ID | Severity | What it checks |
|----|----------|-----------------|
| ACG-001 | P1 | Hand-typed TS interfaces re-declared per feature instead of a generated client from the OpenAPI spec |
| ACG-002 | P1 | Generated client checked in but not regenerated in CI — drifts silently from the live API |
| ACG-003 | P2 | Hand-written `HttpClient` calls duplicate endpoints the generated client already exposes |
| ACG-004 | P2 | No CI step fails the build when the OpenAPI spec changes without the client being regenerated |
---
## Check A — Hand-typed interfaces instead of a generated client (ACG-001)
### Detection
Grep feature directories for TypeScript interfaces that mirror a C# DTO field-for-field
(`interface OrderDto { id: string; customerName: string; total: number }`) declared by hand
in more than one place. Every backend DTO is a generation target from the OpenAPI spec that
`dotnet-dto-mapping` already produces — hand-typing it a second time on the frontend means
the two copies drift the moment either side adds a field, and nothing catches it until a
runtime `undefined`.
### BAD — TS interface hand-typed to match the backend DTO, redeclared per feature
```typescript
// features/orders/models/order.model.ts
export interface Order {
id: string;
customerName: string;
total: number;
}
// features/order-history/models/order-summary.model.ts — a second, slightly different hand-typed copy
export interface OrderSummary {
id: string;
customer: string; // renamed by hand, now inconsistent with the actual DTO field name
total: number;
}
```
### GOOD — generated client is the single source of truth for request/response types
```typescript
// nswag.json generates this file — never hand-edited
import { OrderDto, OrdersClient } from '@myorg/api-client';
@Injectable({ providedIn: 'root' })
export class OrderService {
private readonly client = inject(OrdersClient);
getOrder(id: string): Observable<OrderDto> {
return this.client.getOrder(id); // OrderDto's shape always matches the live C# contract
}
}
```
---
## Check B — Generated client checked in but never regenerated (ACG-002)
### Detection
Check whether the generated client directory (`libs/api-client` or similar) has a `git log`
history that lags behind backend DTO changes, and whether there's an `npm run generate-api`
(or equivalent NSwag CLI invocation) wired into a pre-build/CI step versus only ever run
manually by whoever happened to remember. A generated file that's manually regenerated
"when someone remembers" is worse than no generation at all — it creates false confidence
that the types are current.
### BAD — generation script exists but nothing calls it automatically
```json
// package.json
{ "scripts": { "generate-api": "nswag run nswag.json" } }
// no pre-build hook, no CI step — relies on a developer remembering to run this after every backend change
```
### GOOD — regeneration wired into CI, fails the build on drift
```yaml
# .github/workflows/ci.yml
- name: Regenerate API client
run: npm run generate-api
- name: Fail if generated client is stale
run: git diff --exit-code libs/api-client || (echo "API client is out of date — run npm run generate-api" && exit 1)
```
---
## Check C — Hand-written HttpClient calls duplicate the generated client (ACG-003)
### Detection
Grep for `HttpClient.get<T>(...)`/`.post<T>(...)` calls hitting an endpoint the generated
client (Check A) already exposes a typed method for. Bypassing the generated client for
"just this one call" reintroduces exactly the drift risk the codegen exists to prevent, and
skips whatever cross-cutting interceptor behavior (`angular-http-resilience` retry/timeout,
correlation-ID header) is wired onto the generated client's `HttpClient` instance.
### BAD — raw HttpClient call sidesteps the generated client
```typescript
loadOrder(id: string): Observable<any> {
return this.http.get<any>(`/api/v1/orders/${id}`); // untyped, bypasses OrdersClient entirely
}
```
### GOOD — always go through the generated client
```typescript
loadOrder(id: string): Observable<OrderDto> {
return this.ordersClient.getOrder(id); // typed, shares the same HttpClient pipeline/interceptors
}
```
---
## Check D — No CI drift check on spec change (ACG-004)
### Detection
Confirm the backend publishes its OpenAPI spec as a build artifact (or serves it at a known
route in CI against a running instance) and the frontend's codegen step consumes that exact
artifact — not a stale spec file committed to the frontend repo by hand. Without this, a
backend PR that renames a DTO field ships with no signal to the frontend team until a bug
report comes in.
### BAD — frontend generates against a manually-copied, possibly stale spec file
```json
// nswag.json
{ "documentGenerator": { "fromDocument": { "url": "./openapi-spec-copy.json" } } }
// this file was copied in by hand six months ago and never updated
```
### GOOD — codegen pulls the live spec from the backend's CI artifact/running instance
```json
// nswag.json
{ "documentGenerator": { "fromDocument": { "url": "https://api-ci.internal.example.com/swagger/v1/swagger.json" } } }
```
---
## API client codegen checklist
- [ ] All request/response TypeScript types come from a generated client (NSwag/openapi-typescript), not hand-typed interfaces
- [ ] Client regeneration is wired into CI/pre-build, not a manually-remembered script
- [ ] CI fails the build if the generated client is out of date relative to the backend spec
- [ ] No hand-written `HttpClient` calls duplicate an endpoint the generated client already exposes
- [ ] Codegen points at the backend's live/CI-published OpenAPI spec, not a stale committed copy
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!