Collapse
A REST API for exploring country information built with TypeScript, PostgreSQL, and Drizzle ORM.
This API provides access to country data including regions, languages, currencies, and demographics through a clean RESTful interface.
- TypeScript
- Node.js
- PostgreSQL
- Drizzle ORM
- Docker
- Jest
-
Regions: Geographic regions (e.g., Europe, Asia)
- Has many subregions
- Has many countries
-
Subregions: Geographic areas within regions (e.g., Northern Europe)
- Belongs to one region
- Has many countries
-
Countries: Individual countries with properties
- Belongs to one region
- Belongs to one subregion
- Has many languages (many-to-many)
- Has many currencies (many-to-many)
-
Languages: Languages spoken worldwide
- Used in many countries (many-to-many)
-
Currencies: Currencies used worldwide
- Used in many countries (many-to-many)
Many-to-many relationships are implemented through junction tables:
country_languages
: Links countries to their languagescountry_currencies
: Links countries to their currencies
- Start DBs
docker-compose up -d
- Set up DBs:
npm run db:push
npm run db:push:test
- Install:
npm install
- Start the webserver:
npm run dev
- Test:
npm run test
Do not run npm audit fix --force
. It will install an outdated version of drizzle-kit
.
Documentation available at http://{ENV.HOST}:{ENV.PORT}/documentation
I have implemented a simple Country Explorer API for this task. The application imports data from restcountries.com and provides endpoints to query this information.
Rather than creating individual API calls for each country, I implemented a bulk creation that imports all countries at once from the all countries endpoint:
- Fetches data from restcountries.com
- Transforms the data to match the database schema, selecting only the fields we need
- Performs a batch insert into the database
The bulk import automatically runs when the database is empty, populating it with all countries.
I considered adding a dedicated import script (e.g.,npm run import
) but prioritized other features instead.
I chose to implement a single, powerful GET /api/countries
endpoint with comprehensive filtering, sorting, and pagination capabilities. This endpoint supports:
- Filtering by region, subregion, population, area, language, currency, etc.
- Sorting by various fields in ascending/descending order
- Pagination with customizable page size
However, this led to more complex code, particularly around joins and filtering.
The implementation of the getCountries
method became quite complex due to:
- Multiple table joins required for the relationships (regions, subregions, languages, currencies)
- The need to support various filtering parameters
- Limitations in Drizzle ORM’s current beta version
I initially hoped to use Drizzle’s findMany
feature with relationship queries, but this wasn’t feasible with the current version, requiring more manual query construction.
I could have opted for splitting the API into multiple dedicated endpoints:
GET /api/countries/byregion/{name}/?{filtering&sorting}
GET /api/countries/bylanguage/{name}/?{filtering&sorting}
GET /api/countries/bycurrency/{name}/?{filtering&sorting}
GET /api/countries?populationMin=10000000
GET /api/countries?populationMax=5000000
GET /api/countries?region=Europe
GET /api/countries?subregion=Western%20Europe
GET /api/countries?language=eng
GET /api/countries?name=united
GET /api/countries?currency=EUR
GET /api/countries?sortField=name
GET /api/countries?sortField=name&sortDirection=desc
GET /api/countries?sortField=capital
GET /api/countries?sortField=population
GET /api/countries?sortField=population&sortDirection=desc
GET /api/countries
GET /api/countries?page=2
GET /api/countries?page=1&pageSize=10
Unit tests (mocked) run on separate DB.
npm run db:show
npm run db:show:test
Routes use Boom for errors and Pino for logs, currently only in countriesRoutes.ts
. I’ll add a global error handler and a centralized Pino logger in server.ts
and the API service.
Caching isn’t implemented yet, but i was planning to use Redis with Catbox.
Although Hapi recommends Joi, but would be better switching to Zod for its features (infer types).