In this guide
On this page ⌄
- Key takeaways
- Start with what the repo runs
- Process entry points by language
- HTTP entry points: routes
- Background entry points: workers, consumers and schedules
- CLI entry points
- Frontend entry points
- Library entry points
- Build config as an entry point for understanding
- A checklist for any repo
- Letting a map do the first pass
The fastest way into an unfamiliar codebase is through its front doors. An entry point is any place where the outside world causes code to run: a process starting, an HTTP request arriving, a command typed at a shell, a message landing on a queue, a timer firing, a user clicking a button. Find those, and you can follow execution outward into the rest of the system. Start anywhere else, and you are reading helpers that make no sense without their callers.
Most repositories have more than one entry point, and the important ones are not always where you expect. This guide covers how to find each kind, with search commands for common stacks. It pairs with the broader method in how to read an unfamiliar codebase, where finding the entry point is step three.
Key takeaways
- Start with what the repo produces: run scripts, Dockerfiles, CI deploy jobs and package manifests list every process and its start command.
- The start command names the process entry point. Follow it to
main, the server bootstrap, or the framework's convention. - HTTP entry points are routes. Find the router or the framework's file-based routing convention and list them all.
- Background entry points (workers, consumers, cron) are easy to miss and often hold critical logic. Search for queue, schedule and worker registrations.
- Libraries have API entry points, not process ones. Look at
exports,__init__.py,lib.rsor the public package. - Build config is an entry point for understanding: it defines what gets compiled, bundled and shipped.
Start with what the repo runs
Before searching source, find the commands that start things. These files list them:
| File | What it tells you |
|---|---|
package.json (scripts, main, bin, exports) | Dev and start commands, CLI binaries, library entry |
Dockerfile (CMD, ENTRYPOINT) | Exactly what runs in production |
docker-compose.yml | Every process and its dependencies |
Procfile | Process types on Heroku-style platforms (web, worker, release) |
Makefile, justfile, Taskfile.yml | Build and run targets |
CI config (.github/workflows/*.yml) | What gets built, tested and deployed |
| Kubernetes or Helm manifests | Deployed workloads, including cron jobs |
pyproject.toml ([project.scripts]) | Python CLI entry points |
Cargo.toml ([[bin]], src/main.rs) | Rust binaries |
go.mod plus cmd/ folders | Go binaries, one per subfolder by convention |
A quick sweep:
# Node: scripts and binaries
jq '.scripts, .bin, .main, .exports' package.json
# What production actually runs
rg -n "^(CMD|ENTRYPOINT)" --glob 'Dockerfile*'
# Every process in compose
yq '.services | keys' docker-compose.yml
# Python console scripts
rg -n -A5 "\[project.scripts\]" pyproject.toml
The production start command is the most trustworthy fact in the repo. Documentation drifts; the CMD line in the Dockerfile that ships is what actually runs.
Process entry points by language
Once you know the start command, follow it to the first line of code that runs.
Go. Every binary has a package main with func main(). By convention, each binary lives in cmd/<name>/main.go.
rg -l "^package main$" --type go
main in Go programs usually does wiring: load config, build dependencies, register routes, start servers. Read it top to bottom; it is a table of contents for the process.
Node and TypeScript. The start script names a file (node dist/server.js, tsx src/index.ts). For frameworks, the convention replaces the file: Next.js uses the app/ or pages/ directory, NestJS bootstraps in src/main.ts with NestFactory.create, Express apps usually have a file that calls app.listen.
rg -n "\.listen\(|createServer\(|NestFactory\.create" --type ts --type js
Python. Look for if __name__ == "__main__":, the [project.scripts] table, manage.py for Django, a wsgi.py or asgi.py that defines the application object, or the module passed to gunicorn, uvicorn or celery in the start command.
rg -n "__name__ == .__main__.|FastAPI\(|Flask\(__name__\)|get_wsgi_application|get_asgi_application" --type py
Java and Kotlin. Search for public static void main or, in Spring Boot, @SpringBootApplication. Spring wires most things through annotations, so the main class is short; the real entry points are controllers and listeners.
Rust. src/main.rs for the default binary, src/bin/*.rs for additional ones, src/lib.rs for the library crate. In a workspace, check each member's Cargo.toml.
Ruby on Rails. config.ru boots the Rack app; config/routes.rb lists HTTP entry points; bin/rails and lib/tasks/*.rake define commands.
HTTP entry points: routes
For a web service, each route is an entry point. Get the full list early. Many frameworks can print it for you:
# Rails
bin/rails routes
# Django (requires django-extensions)
python manage.py show_urls
# Laravel
php artisan route:list
# Spring Boot with Actuator mappings endpoint enabled
curl localhost:8080/actuator/mappings
When there is no command, search for the registration pattern:
# Express, Fastify, Koa-router style
rg -n "\.(get|post|put|patch|delete)\(['\"]/" --type ts --type js
# FastAPI and Flask decorators
rg -n "@(app|router|bp)\.(get|post|put|delete|route)\(" --type py
# Go net/http and popular routers
rg -n "HandleFunc\(|\.Handle\(|r\.(Get|Post|Put|Delete)\(" --type go
# Spring
rg -n "@(Get|Post|Put|Delete|Request)Mapping" --type java --type kotlin
For file-based routing frameworks, the folder tree is the route table. In the Next.js App Router, every page.tsx is a page and every route.ts is an API handler; folders in parentheses like (marketing) are route groups that do not appear in the URL. Also check for middleware (middleware.ts in Next.js, app.use in Express, MIDDLEWARE in Django settings), which runs before every matching route and often holds authentication and redirects.
Once you have the route list, pick the one most relevant to your task and follow it. The full walk from route to database is in tracing a request through a web app.
Background entry points: workers, consumers and schedules
Background work is the most commonly missed entry point. It runs in a separate process, nobody visits it in a browser, and it often holds important logic: billing runs, email sending, data syncs, cleanup jobs.
# Queue workers and consumers
rg -n "new Worker\(|\.process\(|@celery\.task|@shared_task|@app\.task|sidekiq_options|perform_later|@KafkaListener|@RabbitListener|\.subscribe\("
# Scheduled jobs
rg -n "cron|schedule\.every|@Scheduled|beat_schedule|CronJob|node-cron|setInterval\(" -i
# Kubernetes scheduled workloads
rg -n "kind: CronJob" --glob '*.y*ml'
Also check platform config outside the code: vercel.json cron entries, cloud scheduler definitions in Terraform, GitHub Actions workflows with schedule: triggers. A job defined in infrastructure code is still an entry point into your application.
CLI entry points
For command-line tools, each subcommand is an entry point. Find where commands are registered:
# Node: commander, yargs, oclif
rg -n "\.command\(|yargs|class .* extends Command" --type ts --type js
# Python: click, typer, argparse
rg -n "@click\.(command|group)|typer\.Typer\(|add_subparsers\(" --type py
# Go: cobra
rg -n "&cobra\.Command\{" --type go
# Rust: clap derive
rg -n "#\[derive\(.*Parser|#\[command\(" --type rust
Running the tool's help output is often faster than reading the registration code: mytool --help and mytool <subcommand> --help give you the command tree, and the command name is a good search term to find its handler.
Frontend entry points
In a single-page app, the process entry is the file that mounts the root component, often src/main.tsx or src/index.tsx with createRoot(...).render(<App />). The bundler config names it: look for input in vite.config.ts (Vite defaults to index.html and follows its script tag), entry in webpack.config.js.
The more useful entry points in a frontend are user actions. Search for the visible text of the button or form you care about, find the component, and follow its event handler to the API call. The API path then leads you to the backend route. That chain of literal searches crosses the whole stack in a few minutes.
Library entry points
A library has no process to start; its entry point is its public API. Look at:
package.jsonexportsandmainfields in JavaScript__init__.pyre-exports in Python packagessrc/lib.rsand itspub usestatements in Rust- Exported (capitalized) identifiers in a Go package's root
- The
index.tsbarrel file of each package in a TypeScript monorepo
The public API tells you what the library promises. Read the exported functions and types first, then follow each into its implementation as needed.
Build config as an entry point for understanding
Build configuration is not executed by users, but it defines what exists at runtime. tsconfig.json paths, next.config.mjs, vite.config.ts, Bazel BUILD files, build.rs in Rust, and Gradle scripts can add code generation, aliases, environment-specific files and plugins that change what runs. If imports look impossible (@/lib/foo with no lib folder at the root), the answer is in the build config. Read it early, at least to know what aliases and generated files exist.
A checklist for any repo
[ ] Production start command(s) found (Dockerfile CMD, Procfile, deploy config)
[ ] Every deployable process listed (web, workers, schedulers, CLIs)
[ ] Process main / bootstrap file read top to bottom
[ ] Full route list obtained (framework command or search)
[ ] Global middleware identified
[ ] Background workers and consumers listed with their queues/topics
[ ] Scheduled jobs listed, including ones defined in infra or CI
[ ] Frontend mount point and bundler entry identified
[ ] Build aliases and code generation steps noted
Letting a map do the first pass
Searching by hand works everywhere, but on a large repo the first pass takes a while. A structural map can shorten it: when a repo's import graph is laid out, entry points tend to stand out as the nodes that import a lot and are imported by nothing. OpenVisio's Explore space shows that architecture view next to the file tree for any GitHub repo, and coding agents can ask the same question over MCP with tools like get_repo_skeleton, as described in an MCP server for codebase context.
Whichever route you take, finish by writing the list down: every process, every route group, every worker and every schedule, each with the file that defines it. That list is the index for everything else you will read.

