This project uses a CLI frontend with a REPL loop to fetch data from the PokéAPI. TypeScript structures maintain session state and cache API responses.
Project Demo:
- Frontend: TypeScript (CLI / REPL-based)
- Backend: n/a (consumes the public PokéAPI)
- Runtime: Node.js v22.15.0+
- Tooling: npm, TypeScript, Vitest
pokedex-cli/
├── public/ # Media assets
├── src/
│ ├── main.ts # Application entry point
│ ├── state.ts # Centralized session state shared across commands
│ ├── repl.ts # REPL loop handling user input and output
│ ├── command-records.ts # CLI command definitions and command registry
│ ├── command-functions.ts # Business logic executed by CLI commands
│ ├── pokeapi.ts # HTTP client for interacting with the PokéAPI
│ ├── pokeapi.types.ts # TypeScript types and schemas for PokéAPI responses
│ └── cache.ts # In-memory cache to prevent redundant API calls
├── .gitignore # Git ignore rules
├── .env # Environment variable configuration
├── .nvmrc # Node.js version specification
├── package.json # Project metadata and dependencies
├── package-lock.json # Dependency lockfile
├── tsconfig.json # TypeScript compiler configuration
├── repl.log # Logged REPL session output
└── README.md # Project documentation
# Before running this project locally, ensure you have the following installed:
- IDE (VS Code, PyCharm, etc.)
- Install Python 3.10+ version: visit python.org/downloads/
- Node.js v22.15.0 or higher (version specified in .nvmrc)
# Dev Dependencies
- TypeScript: static typing and compilation
- @types/node: Node.js type definitions for TypeScript
- Vitest: unit testing framework
# Dependencies
- dotenv: loads environment variables from a .env file
- zod: runtime schema validation and type-safe data parsingThis repo will later be, if not already, saved as a subfolder. Be sure to only clone relevant files. Then, do the following:
- Clone repository
- Install NVM
- Activate v22.15.0 from
.nvmrcfile >>nvm use - Initialize Node.js project >>
npm init -y - Install dev dependencies >>
npm install -D @types/node typescript vitest - Install dependencies >>
npm install dotenv zod - Configure
tsconfig.json>> provided comments in file for guidance - Configure
package.json>>"type"property arleady set and included four"scripts": {}
Step #3 creates the package.json file. Steps #5-6 add node_modules/ and package-lock.json, and update package.json with the installed dependencies.
The app uses environmental variables. APP_PROMPT sets the REPL prompt; CACHE_INTERVAL_MS controls how frequently data is cached; and BASE_LOCATION_URL and BASE_POKEMON_URL define the PokéAPI endpoints for fetching locations and Pokémon data. These are publicly visible. Hiding them in the .gitignore file is unnecessary since no API keys are used in this project.
You can configure this file as follows:
node_modules/
dist/
repl.logStart the program:
npm run dev
Pokédex > COMMAND [OPTIONAL ARGUMENTS]
help>> Displays a help messagemap>> Displays the next 20 location area namesmapb>> Displays the previous 20 location area namesexplore LOCATION-AREA-NAME>> List Pokémon names found within a location areacatch POKEMON-NAME>> Attempt to catch a Pokémon and add it to your Pokédexinspect POKEMON-NAME>> Display individual Pokémon information from Pokédexpokedex>> Display all caught Pokémon as part of Pokédexexit>> Exits the Pokedex
Functional Requirements:
- CLI frontend accepts user commands
- Commands fetch data from PokéAPI endpoints
- Command actions include: listing location areas, viewing Pokémon by area, catching Pokémon, inspecting caught Pokémon, and showing caught Pokémon in the Pokédex
- Stronger Pokémon should be more difficult to catch
Optional Requirements:
- Verbose mode: include additional metadata in command output
- Low latency: aim for response times under ~200ms
- Scalability: support up to ~1M daily active users (DAU)
- CAP consideration: prioritize consistency (accurate state and cache) over availability in failure scenarios
- Cache management: save new API responses, purge stale data based on cache interval
Environment Settings:
- APP_PROMPT (string) — CLI prompt displayed to the user
- CACHE_INTERVAL_MS (number) — duration (ms) for caching API responses
- BASE_LOCATION_URL (string) — PokéAPI endpoint for location areas
- BASE_POKEMON_URL (string) — PokéAPI endpoint for individual Pokémon
CLI Commands (CLICommand):
export type CLICommand = {
name: string;
description: string;
callback: (state: State, ...args: string[]) => Promise;
};
Available Commands:
- help — displays help message
- map/mapb — lists next/previous location areas
- explore — lists Pokémon in a location area
- catch — attempts to catch a Pokémon and add to Pokédex
- inspect — displays details of a caught Pokémon
- pokedex — lists all caught Pokémon
- exit — closes the CLI
PokéAPI Data Types:
- Locations — paginated list of location areas
- LocationArea — individual location area with Pokémon encounters
- Pokemon — individual Pokémon with stats, types, and base attributes
Cache State:
export type CacheEntry = {
cachedAt: number; // timestamp when this API response was cached
response: T; // the actual PokeAPI response
};
CLI State:
- repl (Interface) — Node readline interface for user interaction
- commands (Record<string, CLICommand>) — all available CLI commands and their callbacks
- nextLocationsURL / prevLocationsURL (string | null) — pagination state for location areas
- pokedex (Record<string, Pokemon>) — caught Pokémon, keyed by name
- pokeApiCache (PokeApiCache) — in-memory cache for API responses
Command-Line Interface (CLI):
npm run start
Pokedex > user_command [optional arguments]
Command Calls:
command.callback(state: State, ...args: string[])
helperFunction(requestURL: string | null) -> Promise<ApiCallResult<T>>
- Program state includes a repl interface and commands instance:
- The REPL contains logic to decide what function to call:
- Parses user input into tokens with
cleanInput(input: string) - Assigns first token to
keyand remaining tokens toargs - Searches for
keyin state:commands: Record<string, CLICommand> - Matches will invoke callback functions asynchronously:
command.callback(state, ...args) - Callback functions will use
argsto construct endpoint URL
- Parses user input into tokens with
- Helper functions use Zod library to validate raw JSON responses against typed schemas
- Helper functions standardize output and error handling with result type pattern:
({ success: true; data } | { success: false; error })
Cache Management:
state.pokeApiCache.getResponse(requestURL) -> CacheEntry | undefined
state.pokeApiCache.addResponse(requestURL, apiResponse)
- Program state includes a pokeApiCache instance:
- Command and helper functions consume cache data transparently
- The program state saves cache data as a map:
Map<string, CacheEntry<any>>()
- Each cached entry is a
CacheEntry<T>containing:cachedAt— timestamp (ms) when the API response was storedresponse— the actual API response data (T)
getResponse(url): program checks cache prior to making new API callsaddResponse(url): program updates cache on each new API call- If a cached entry exists, the response is returend instantly; otherwise,
fetchAndCache()makes a new API call and updates the cache - Stale entries are automatically purged in the background by the private
#reap()method, usingCACHE_INTERVAL_MS
State Management:
initState()// initializes REPL, commands, pagination URLs, cache, and pokedex
- State object instance created at startup by
initState() - REPL needs state to be initialized:
- REPL does not mutate state directly, it only orchestrates command callback functions
- Command callback and helper functions may read/update state (Pokédex, pagination, cache)
User Input → REPL → Tokens
Pokedex > user_command [optional arguments]
cleanInput(input)→ key, args
- REPL reads user input and splits it into token words
- First token assigned to
key: stringand used to lookup corresponding CLICommand:state.commands[key] - Remaining tokens assigned to
args: string[]and passed to found callback functions on lookup
Callback Function Calls
command.callback(state, ...args) → Promise<void>
- Each CLICommand found on lookup has a callback function to invoke in
command-functions.ts - Callback functions for some commands (
help,exit,pokedex) may be executed without making API calls - Remaining callback functions construct requestURL argument for API calls
- Cache always checked prior to making new API calls
Cache Calls
state.pokeApiCache.getResponse(requestURL) → CacheEntry<any>
fetchAndCache(state, PokeAPI.apiCallFunction, requestURL) → Promise<T>
- Initial cache search made by
getResonse()- If found,
CacheEntry.responsecontains response data
- If found,
- If not found,
fetchAndCache()helper function will update cache- Will make and store API call:
result = await apiCallFunction(requestURL) - Will update cache:
state.pokeApiCache.addResponse(requestURL, cacheResult)
- Will make and store API call:
- Measured metrics show that cached responses are over 7,000x faster than the average new API call (~0.01 ms vs ~ 77ms for uncached requests)
- Stale cache entries are removed automatically in the background
API Calls
apiCallFunction(requestURL) → Promise<T>
fetchApi(requestURL, PokeTypes.CustomSchema) → Promise<T>
- The
fetchAndCache()function internally callsapiCallFunction(requestURL) - The API call function also internally calls
fetchApi(requestURL, PokeTypes.CustomSchema) - JSON responses are validated against typed schemas built with Zod library
The project currently represents a single-node, stateful CLI application that runs on the client machine. The program uses a REPL with shared state to orchestrate function calls. No backend server or persistent database is required; instead, in-memory state and cache storage are used. The project could be improved to be more robust in the following manner:
- Client: Build frontend with HTML and JavaScript. Include input forms for users to submit commands.
- API Gateway: This is a single, centralized entry point and security perimeter to the backend servers. Security benefits include handling authentication/authorization and rate limiting. Scalability benefits include horizontal load balancing and service routing.
- Server: A Node.js server would receive requests from the client and run the command logic that currently lives in the CLI. Making the server stateless would allow multiple server instances to run at once and scale with user traffic.
- Database: May use open-source Memcached and PostgreSQL solutions for cache and database needs, respectively.
Boot.dev provided the project requirements and guidance to complete this project. Contributions are welcome! Feel free to report any problems.
