Mo Sharif
← ~/blog

How I Generate Architecture Diagrams from a GitHub Repo

A user DMed me on Twitter last October. Simple message: "Can I just paste my repo URL and see the architecture?"

I stared at it for about ten seconds. Then I closed Twitter, opened my editor, and started building. That DM became the most-requested feature on Codelit, and honestly the most fun I've had building anything in years.

A GitHub repo analyzer

is a parser that reads a repository's configuration files — package manifests, Dockerfiles, infrastructure-as-code, CI workflows — and infers the services, datastores, and connections that make up its architecture.

Your codebase already contains a complete description of your architecture. It's just scattered across a dozen config files that nobody reads together.

Your package.json tells me your frameworks and external services. Your Dockerfile tells me how you deploy. Your docker-compose.yml literally maps out your service topology. Terraform files describe your cloud infra. .github/workflows reveal your CI/CD pipeline.

The information is all there. It's just never been assembled into a picture.

So I built a parser that does exactly that. You paste a GitHub repo URL into Codelit's GitHub analysis feature, and it reads through your codebase, detects your stack, and generates an interactive architecture diagram. No manual drawing, no stale Lucidchart from 2023, and no prompt to write — that's the other route into the same canvas, where you describe a system in plain English and let the AI pipeline draw it.

The Codelit analyzer looks for what I call architectural signal files, and ignores everything else in the repository on the first pass. Six groups, in priority order.

Signal groupFiles it looks forWhat it reveals
Package manifestspackage.json, requirements.txt, pyproject.toml, go.mod, Cargo.toml, pom.xmlFrameworks and external services
Container configsDockerfile, docker-compose.ymlHow you deploy, and your service topology
Infrastructure as code*.tf, pulumi.*, CloudFormation templatesThe cloud infrastructure you provision
CI/CD.github/workflows/*.yml, .gitlab-ci.ymlYour pipeline, and where it deploys to
Database configsprisma/schema.prisma, knexfile.js, migration dirsWhich database engine, and its schema
API specsopenapi.yaml, swagger.json, GraphQL schema filesService boundaries and contracts

But finding these files is the easy part. The interesting bit is what I infer from them.

A dependency is a claim about infrastructure. A package.json with @prisma/client doesn't just tell me you use Prisma — it tells me you've got a relational database, and if I also find prisma/schema.prisma, I read the datasource block and know exactly whether it's PostgreSQL or MySQL.

Dependency signalWhat the parser drawsWhy that reading
@prisma/clientA relational databaseThe datasource block decides Postgres vs MySQL
mongooseMongoDBOnly ever talks to one engine
redis + bull or bullmqRedis as a message queueA job library means work is queued, not cached
redis + connect-redisRedis as a session storeSame server, different architectural role
ioredisRedis as a cacheNothing alongside it narrows the reading
@aws-sdk/client-s3S3 file storageA managed service you consume, not run
@sendgrid/mailAn email delivery serviceExternal, at the edge of the system
kafkajsKafka event streamingPublishers and consumers become async edges

The nuance matters. redis on its own could mean five different things, which is why it appears three times in that table. The parser reads combinations, not individual packages.

For ambiguous cases, I send the relevant code snippets to the LLM with a structured prompt, and it classifies the architectural role when the heuristics alone can't decide. It's a hybrid: fast and deterministic for the 80% case, AI-assisted for the rest.

Yes. Every ecosystem gets its own mapping table, because I didn't want this to be a Node.js-only party. The parsing is language-specific; the output is identical — architecture nodes with types, and edges between them.

EcosystemManifests readWeb layerData layerAsync and infra
JavaScript / TypeScriptpackage.jsonNext.js, ExpressPrisma, MongooseBull/BullMQ, ioredis, kafkajs
Pythonpyproject.toml, requirements.txt, setup.pyFlask, Django, FastAPISQLAlchemy, AlembicCelery, boto3 for AWS services
RustCargo.tomlactix-web, axumdiesel, sqlxtokio for async, lapin for RabbitMQ
Gogo.modgin, echo, fibergormsarama for Kafka

Mono-repos are the hardest input this thing takes. A single-service repo has one manifest and one answer; a mono-repo means finding the service boundaries first, then working out which services talk to each other, before anything can be drawn.

Detection is the first challenge. Sometimes there's a lerna.json or pnpm-workspace.yaml. Sometimes it's just multiple package.json files at different directory depths. My heuristic: if I find more than one package manifest at different levels, it's a mono-repo.

Then I treat each manifest as a potential service boundary and analyze them independently. But the real headache is figuring out how services talk to each other. Service A might import from a shared lib. Service B might call Service A's API. Service C might consume events from Service A.

I look for HTTP client calls, message queue publishers/consumers, and shared database connections. If two services reference the same database URL env var, they share a database, which I flag as an architectural smell in the diagram. Because it usually is one.

No. Codelit requests read-only GitHub OAuth access, the token is encrypted, it is used only during analysis, and it is not persisted. The parser reads the signal files, extracts what it needs, and discards everything else. Your source code never lands in a database of mine.

Public repos were easy — GitHub's API lets you read files without auth. Private repos need OAuth, which meant building a whole auth flow I hadn't planned for.

I learned quickly that transparency is non-negotiable here. Engineers are rightfully paranoid about third-party tools touching their code. So I added a detailed permissions page and a log showing exactly which files were accessed during analysis.

Detection produces a list of components and relationships; the mapping pipeline turns that list into a picture in five ordered steps.

  1. Detected tech becomes architecture nodes (with types like database, service, queue, cache)
  2. Inferred relationships become edges (sync calls, async messages, data flow)
  3. Nodes get assigned to tiers: client, edge, application, data, external
  4. Layout algorithm positions nodes to minimize edge crossings within tiers
  5. React Flow canvas renders everything as an interactive diagram you can drag around

The layout follows conventions: clients at top, load balancers in front of services, databases at the bottom, queues between producers and consumers. It's not perfect every time, but it's a solid starting point.

After that, an analyzed repo is just a Codelit diagram, on the same canvas you land on when you paste in Mermaid syntax.

Paste https://github.com/your-startup/main-app and about eight seconds later you have an interactive diagram of that repo, already wired together.

Output: An interactive diagram showing:

  • Next.js frontend
  • Express API server (detected from package.json)
  • PostgreSQL database (detected from Prisma schema)
  • Redis for caching + Bull for job queue (detected from dependency combo)
  • S3 for file uploads (detected from @aws-sdk/client-s3)
  • GitHub Actions deploying to AWS ECS (detected from workflow files)

All auto-connected with proper edges showing data flow. And if the drawing matches what you meant to build, you can export it as real Docker Compose, Terraform, and Kubernetes manifests.

Three repo shapes break the happy path for the analyzer, and each needed its own fallback.

Repos with no clear structure: Some codebases are just a pile of files in root. No Dockerfile, no IaC, no CI/CD. The parser still finds value in the package manifest, but the diagram is sparse. I generate what I can and suggest what's missing.

Massive repos: Repos with 10,000+ files need rate limiting on the GitHub API. I implemented a priority queue: signal files first, then drill into specific directories only if initial signals are ambiguous.

Polyglot repos: A repo with both package.json and requirements.txt at root. Is it a Node frontend with a Python backend? Or a Node build tool with Python app code? Directory structure and import patterns help disambiguate.

Onboarding, not documentation. The most common use of repo analysis is a new engineer pasting the repo URL in their first week and getting an instant map of what they're about to work on. That's worth more than any wiki page. Architecture reviews come second.

"I finally have a diagram of what we actually built." That's the reaction I keep hearing. Not excitement about the technology. Relief that someone finally documented the system.

I pointed the analyzer at a friend's startup repo, a mono-repo with four services. It correctly identified the Next.js frontend, Node.js API, Python ML service, background job worker, PostgreSQL, Redis (caching + queuing), S3, and GitHub Actions deploying to ECS. He stared at the screen and said, "We don't have this documented anywhere."

Teams use the generated diagram as a conversation starter: "Why is this service hitting the database directly? Shouldn't it go through the API?" Those conversations are much easier when everyone is looking at the same picture, and much harder to dodge when the picture came out of the code instead of somebody's slide deck.

Try it with your own repo: Codelit GitHub Analysis. I genuinely want to know what it gets wrong. Every edge case makes the parser smarter.

Questions people actually ask

How do you generate an architecture diagram from a GitHub repository?
You read the configuration files that already describe the system. Package manifests name the frameworks and external services, Dockerfiles and compose files describe deployment and service topology, infrastructure-as-code files describe cloud resources, and CI workflows describe deploy targets. A parser turns those signals into typed nodes and edges, assigns each node to a tier, and lays the result out as a diagram. No manual drawing is involved.
Does Codelit store my code when it analyses my repository?
No. The GitHub token is encrypted, used only during the analysis run, and is not persisted. The parser reads the architectural signal files, extracts the dependencies and configuration it needs, and discards everything else. Codelit also shows a permissions page and a log of exactly which files were accessed during the run, because engineers are right to be careful about third-party tools touching a private codebase.
Can it analyse a private GitHub repository?
Yes, through GitHub OAuth. Public repositories need no authentication at all, because the GitHub API serves their files to anyone. Private repositories require you to grant read-only access to your repositories, which is what the OAuth flow does. Access is read-only, the token is encrypted, and it is used only for the duration of the analysis.
Does it work with mono-repos?
Yes, and mono-repos are the hardest case. If more than one package manifest exists at different directory depths, the repository is treated as a mono-repo and each manifest becomes a candidate service boundary. Relationships between those services are inferred from HTTP client calls, message queue publishers and consumers, and shared database connections. Two services pointing at the same database URL get flagged in the diagram as an architectural smell.
Which languages and frameworks does the analyzer support?
JavaScript and TypeScript through package.json, Python through pyproject.toml, requirements.txt and setup.py, Rust through Cargo.toml, and Go through go.mod. Each ecosystem has its own mapping table, covering web frameworks such as Django, FastAPI, actix-web, axum, gin and echo, database libraries such as Prisma, SQLAlchemy, diesel, sqlx and gorm, and async infrastructure such as Celery, tokio, lapin and sarama. The output shape is identical in every case.
How long does the analysis take?
Around eight seconds from pasted URL to interactive diagram for a typical single-service repository. Very large repositories, in the range of ten thousand files and up, take longer because the GitHub API has to be rate limited. Those runs use a priority queue that fetches architectural signal files first and only drills into specific directories when the initial signals are ambiguous.