This repository contains a NestJS/TypeScript implementation of the challenge described in the document "Back End Challenge (2025).pdf" located at the project root.
- Framework: NestJS 11 (GraphQL, schema-first)
- Database: PostgreSQL (via Prisma ORM)
- Infrastructure: Docker/Docker Compose
- Data ingestion: External XML APIs, exponential retries, controlled concurrency
- Logging:
nestjs-pinowithpino-prettyin development - Configuration: Global configuration module with default values and environment variables
The items below describe the main challenge requirements and how they are covered in this project:
-
GraphQL API for domain entities
- Types and SDL schema in src/resources/make/schema.graphql and src/resources/vehicle/schema.graphql.
- Implemented queries:
makes,make(id),vehicleTypes,vehicleType(id),vehicleTypesByMakeId(makeId). - Mutations:
createMake,updateMake,deleteMake,createVehicleType,updateVehicleType,deleteVehicleType. - Subscriptions:
makeCreated,vehicleTypeCreated(baseline for events).
-
Relationships between entities
Make.vehicleType: [VehicleType!]!resolved in src/resources/make/make.resolver.ts.VehicleType.make: Make!resolved in src/resources/vehicle/vehicle.resolver.ts.
-
Persistence with Prisma + PostgreSQL
- Prisma service in src/prisma/prisma.service.ts with
@prisma/adapter-pgand connection at initialization. - Composite unique key for
VehicleTypeby(makeId, vehicleTypeId)used in upserts (see src/data-ingestion.provider.ts).
- Prisma service in src/prisma/prisma.service.ts with
-
External data ingestion
- Provider in src/data-ingestion.provider.ts:
- Fetches
MakesandVehicleTypesfrom configurable URLs. - XML parsing with
fast-xml-parser. fetchWithRetrywith exponential backoff, jitter and timeout.- Concurrency limited with
p-limit, configurable viaMAX_CONCURRENT_FETCHS. - Automatic execution at application bootstrap.
- Fetches
- Provider in src/data-ingestion.provider.ts:
-
Environment-based configuration with defaults
- Global configuration module: src/config/config.module.ts.
- Reading
PORT,DATABASE_URL, ingestion URLs and retry/concurrency parameters with default values and.envsupport. - Use of
ConfigServiceinmain.tsand providers.
-
Structured logging
nestjs-pinoconfigured in src/app.module.ts and initialization in src/main.ts.- Pretty output in development and appropriate level in production.
-
Docker and Compose
- Production Dockerfile: Dockerfile.
- Compose with
apianddbservices: docker-compose.yml, healthcheck, environment variables and port mapping.
src/app.module.ts: root module with GraphQL, Logger and domain modules.src/modules/*: domain modules, services and resolvers (Make,VehicleType).src/prisma/*: integration with Prisma Client and Prisma module.src/config/config.module.ts: environment aggregation and defaults.src/data-ingestion.provider.ts: ingestion and persistence pipeline.
Create a .env file with your values. Examples of supported variables are listed below.
This step is not mandatory, as default values are set on ConfigModule.
docker compose up --build -dAfter startup, access the Playground: http://localhost:<PORT>/graphql.
The api service exposes port 4000 by default (configurable via PORT). The db service uses postgres:16-alpine with Compose default credentials (default port exposed: 5432).
NODE_ENV:development|test|production(default:development)PORT: API port (default:4000)DATABASE_URL: PostgreSQL connection (default:postgresql://myuser:mypassword@db:5432/pdgb)MAKES_URL: source URL forMakes(default:https://vpic.nhtsa.dot.gov/api/vehicles/getallmakes?format=XML)VEHICLE_TYPES_URL:https://vpic.nhtsa.dot.gov/api/vehicles/GetVehicleTypesForMakeId/{makeId}?format=XMLMAX_MAKES_TO_INGEST: limit ingestion (default:0→ no limit)MAX_CONCURRENT_FETCHS: vehicle types fetching concurrency (default:12)RETRY_ATTEMPTS: retry attempts (default:5)RETRY_BASE_DELAY_MS: backoff base (default:8000)RETRY_MAX_DELAY_MS: max delay (default:800000)
type Make { id, makeId, makeName, vehicleType: [VehicleType!]! }type VehicleType { id, vehicleTypeId, vehicleTypeName, makeId, make: Make! }
Queries:
query {
makes { id makeId makeName vehicleType { vehicleTypeId vehicleTypeName } }
}
query($id: String!) { make(id: $id) { id makeName } }
query($makeId: String!) { vehicleTypesByMakeId(makeId: $makeId) { vehicleTypeId vehicleTypeName } }Mutations (examples):
mutation {
createMake(input: {
makeId: "123",
makeName: "Foo"
}) {
id
makeId
makeName
}
}
mutation {
createVehicleType(input: {
vehicleTypeId: "1",
vehicleTypeName: "SUV",
makeId: "123"
}) {
id
}
}- Automatic execution at bootstrap: src/data-ingestion.provider.ts.
- Retries with exponential backoff and timeout.
- Flow:
getMakes()readsMAKES_URL→ parses XML → normalizes.- For each Make, fetches
VehicleTypesviaVEHICLE_TYPES_URL(replaces{makeId}) withp-limitconcurrency. - Persists via Prisma
upsertusing composite key(makeId, vehicleTypeId).
- Schema:
prisma/schema.prisma(modelsMakeandVehicleType). - Client generated in
generated/prisma. - Composite unique key applied to
VehicleTypeto ensure consistency in upserts.
- Configuration in src/app.module.ts.
- Usage in
main.tsand the ingestion provider for structured logs.