Core Rules

Applies to everything

Before looking at any specific project type, internalize these rules. They apply regardless of language, stack, or scale.

Architecture → Directory Translation

Architecture box
Folder — each major component in your diagram gets its own directory
Responsibility within a box
File — each distinct job inside a component gets its own file
Arrow between boxes
Import / interface boundary — the seam between folders
Swappable component
Its own folder — if it could be replaced independently, isolate it

Name by purpose, not type

rate_limiter.go tells you what it does. utils.go tells you nothing. Name files and folders by the job they perform, not the category of thing they are.

Build what you need now

Don't create folders for things that don't exist yet. A folder with one file in it is a sign you over-structured. Start flat, split when a file gets too large or a second distinct concern appears.

Data flow = folder order

Read your folder list top to bottom — it should roughly match how data flows through your system. If it doesn't, your structure may not match your architecture.

config/ and main are universal

Almost every project needs a place for configuration and an entry point that wires everything together and stays thin. If main grows logic, something is in the wrong folder.


Planning Process

Before you open VS Code

The fastest way to get a good directory structure is to spend five minutes answering questions before creating any files. Architecture first, folders second.

Questions to answer before creating any files
  • What are the major components of this system? Draw them as boxes.
  • What does each component own and what does it do?
  • How does data flow between components? Draw the arrows.
  • What could be swapped out independently? (database, API layer, frontend framework)
  • What external services or libraries does this depend on?
  • What needs to be kept secret? (API keys, credentials → .env)
  • Will this run in multiple environments? (dev, staging, prod → config/)
  • What is the entry point — where does execution begin?

Once you've answered these, your boxes become folders, your answers about what each box does become files, and your arrows become imports. The structure should feel obvious rather than designed.

Step 1 — Sketch the architecture

Even a rough list of components on paper or in a markdown file is enough. You're not designing a system — you're identifying the nouns. "I need something that fetches data, something that processes it, something that stores it, and something that serves it."

Step 2 — Map to folders

Each noun from step 1 becomes a folder. Then ask what the distinct jobs are inside each noun — those become files. Don't go deeper than two levels until you have a reason to.


01

REST API

Go PostgreSQL Any HTTP client

A REST API receives HTTP requests, processes them, talks to a database, and returns responses. The architecture is almost always: router → handler → service/business logic → database. Each layer maps directly to a folder.

HTTP request
middleware/
handlers/
internal/
db/
response
directory structure
my-api/
├── cmd/
│   └── main.go              # entry point — wires everything, starts server
│
├── internal/             # Go convention: not importable by outside packages
│   ├── handlers/
│   │   ├── user_handler.go   # HTTP handlers for /users routes
│   │   └── post_handler.go   # HTTP handlers for /posts routes
│   ├── middleware/
│   │   ├── auth.go           # JWT validation, session checks
│   │   └── logging.go        # request logging
│   ├── models/
│   │   ├── user.go           # User struct, validation
│   │   └── post.go           # Post struct, validation
│   └── service/
│       ├── user_service.go   # business logic for users
│       └── post_service.go   # business logic for posts
│
├── db/
│   ├── queries.go            # SQL queries
│   └── migrations/           # versioned schema changes
│       ├── 001_create_users.sql
│       └── 002_create_posts.sql
│
├── config/
│   └── config.go             # reads .env, exposes typed config struct
│
├── .env                      # DB_URL, JWT_SECRET, PORT — never committed
├── .gitignore
└── go.mod
Plan this before you structure it
  • What resources does this API expose? Each resource likely gets its own handler file.
  • Is there business logic that sits between the handler and the database? That's your service layer.
  • What needs authentication? That's middleware, not handler logic.
  • Will the database schema change over time? You need migrations from day one.
  • What configuration changes between environments? That goes in config/, not hardcoded.

cmd/ vs internal/

Go convention: cmd/ holds entry points (things you run), internal/ holds everything else. Packages inside internal/ can't be imported by code outside the module — enforcing encapsulation at the filesystem level.

Handler vs Service

Handlers deal with HTTP — reading request params, writing responses, status codes. Services deal with business logic — validation, orchestration, rules. If your handler is doing business logic, it's in the wrong layer.


02

Full Stack Web App

TypeScript Vue Go PostgreSQL

A full stack app has two distinct codebases living in one repository — a frontend that runs in the browser and a backend that runs on a server. The top-level split is always client vs server. Everything else follows from that.

Browser
client/ (Vue + TS)
server/ (Go API)
db/
directory structure
my-app/
│
├── client/                   # everything that runs in the browser
│   ├── src/
│   │   ├── components/       # reusable Vue components
│   │   ├── views/            # page-level components (one per route)
│   │   ├── stores/           # Pinia state management
│   │   ├── api/              # functions that call the Go backend
│   │   └── types/            # shared TypeScript interfaces
│   ├── vite.config.ts
│   ├── tsconfig.json
│   └── package.json
│
├── server/                   # everything that runs on the server
│   ├── cmd/
│   │   └── main.go
│   ├── internal/
│   │   ├── handlers/
│   │   ├── middleware/
│   │   ├── models/
│   │   └── service/
│   ├── db/
│   │   └── migrations/
│   └── go.mod
│
├── config/
│   └── .env                  # shared secrets: DB_URL, API keys
│
└── docker-compose.yml        # optional: spin up DB and services together
Plan this before you structure it
  • Are client and server truly separate? If the frontend is server-rendered, the structure changes significantly.
  • How does the frontend call the backend? Define that API contract early — it's the seam between the two halves.
  • What state lives on the client vs the server? Client state goes in stores/, server state goes in the DB.
  • Will you run these as separate services or together? Separate → each gets its own repo eventually.
  • What's shared between client and server? Type definitions, validation rules — consider a shared/ folder.

03

Graphics Application

C++ SFML CMake vcpkg

The defining rule of graphics apps: simulation and rendering are always separate. The code that runs physics, manages entities, and tracks state must never know how it gets drawn. This separation lets you swap rendering backends without touching simulation logic — and it keeps your mental model clean.

main loop
update simulation
pass data to renderer
draw + display
repeat
directory structure — particle system example
particle-sim/
│
├── src/
│   ├── main.cpp              # entry point — loop only, no logic
│   │
│   ├── simulation/           # knows nothing about rendering
│   │   ├── emitter.h
│   │   ├── emitter.cpp       # spawns particles, manages lifetime
│   │   ├── physics.h
│   │   └── physics.cpp       # gravity, velocity, forces
│   │
│   └── renderer/             # knows nothing about simulation logic
│       ├── renderer.h
│       └── renderer.cpp      # owns window, draws what it's given
│
├── assets/               # textures, fonts, sounds
│
├── build/                # generated by CMake — never commit this
│
├── CMakeLists.txt        # describes how to compile the project
├── vcpkg.json            # declares C++ dependencies (SFML, etc.)
└── .gitignore            # must include build/ and .env
Plan this before you structure it
  • What are the entities in your simulation? Each distinct entity type may warrant its own file.
  • What does the renderer need to know? Only what to draw — position, color, shape. Nothing else.
  • What is the game loop doing each frame? Poll events → update → clear → draw → display. Map each step to a method.
  • Are there multiple systems? (physics, AI, audio) Each is a candidate for its own folder inside simulation/.
  • What libraries do you need? List them in vcpkg.json before writing a line of code.

The loop shape is universal

Poll events → update simulation → clear → draw → display. This loop appears in SFML, OpenGL, Vulkan, Unity, and Unreal. Only the complexity inside each step changes. Get comfortable with the shape here.

Delta time, always

Tie all simulation updates to real elapsed time, not frame count. dt = clock.restart().asSeconds(). Pass it into every update function. Physics that runs at the wrong speed on a different machine is a common beginner bug.


04

Discord Bot

TypeScript discord.js Node.js

Discord bots are event-driven — they react to things happening (a message sent, a user joining, a slash command used). The architecture maps directly to that: commands are one folder, events are another, and everything that supports them lives underneath.

Discord event
event handler
command router
command logic
db / external API
directory structure
my-bot/
│
├── index.ts              # entry point — registers events, logs in
│
├── src/
│   ├── commands/             # one file per slash command
│   │   ├── ping.ts
│   │   ├── ban.ts
│   │   └── stats.ts
│   │
│   ├── events/               # one file per Discord event type
│   │   ├── ready.ts          # fires when bot connects
│   │   ├── messageCreate.ts  # fires on every message
│   │   └── guildMemberAdd.ts # fires when someone joins
│   │
│   ├── db/                   # if the bot stores data
│   │   ├── client.ts         # DB connection
│   │   └── queries.ts        # read/write operations
│   │
│   └── lib/                  # shared utilities
│       ├── embed_builder.ts  # reusable Discord embed templates
│       └── permissions.ts    # permission check helpers
│
├── config/
│   └── .env                  # DISCORD_TOKEN, CLIENT_ID, DB_URL
│
├── tsconfig.json
└── package.json
Plan this before you structure it
  • What commands does the bot need? Each one becomes a file in commands/.
  • What Discord events does the bot respond to? Each one becomes a file in events/.
  • Does the bot need to remember anything between sessions? If yes, you need db/.
  • Are there things multiple commands share? (formatting, permission checks) That goes in lib/.
  • Will commands be loaded dynamically or registered manually? Dynamic loading means a command loader utility in lib/.

05

Data Pipeline / ETL

Python Any data source Any data store

ETL stands for Extract, Transform, Load — and the folder structure maps to it almost literally. Data comes in from somewhere, gets cleaned and reshaped, and lands somewhere else. The architecture is a pipeline, and the directory reflects that linearity.

Source (API / file / DB)
extract/
transform/
load/
Destination
directory structure
my-pipeline/
│
├── main.py               # entry point — runs the pipeline
│
├── extract/              # pull raw data from sources
│   ├── api_client.py     # fetch from external APIs
│   ├── file_reader.py    # read CSV, JSON, XML
│   └── db_reader.py      # query source database
│
├── transform/            # clean, reshape, enrich
│   ├── cleaner.py        # remove nulls, fix types, deduplicate
│   ├── normalizer.py     # standardize formats (dates, currency, etc.)
│   └── enricher.py       # join with lookup data, derive new fields
│
├── load/                 # write processed data to destination
│   ├── db_writer.py      # insert/upsert to database
│   └── file_writer.py    # write output files
│
├── scheduler/            # if the pipeline runs on a schedule
│   └── cron.py
│
├── data/
│   ├── raw/              # untouched source data — never modify
│   └── processed/        # output of the pipeline
│
├── config/
│   └── .env              # API_KEY, DB_URL, OUTPUT_PATH
│
└── requirements.txt
Plan this before you structure it
  • Where does the data come from? Each distinct source may need its own extractor file.
  • What transformations are needed? Separate cleaning (fixing bad data) from normalizing (reshaping good data).
  • Where does the data go? Each destination gets its own loader.
  • Does raw data need to be preserved? If yes, never let transform/ overwrite extract/ output — use data/raw/.
  • Does this run once or on a schedule? If scheduled, you need a scheduler and idempotent load logic.

06

Game Project

Godot GDScript C++ (GDExtension)

Godot uses its own virtual filesystem (res://) that mirrors your project folder. The engine doesn't enforce a structure, which means you have to impose one yourself. The most important rule: organize by feature or entity type, not by file type. Putting all scripts in one folder and all scenes in another produces a structure that fights against how you actually work.

directory structure
my-game/            # this is res:// in Godot
│
├── scenes/           # scene files (.tscn), organized by type
│   ├── world/        # levels, maps, environments
│   ├── entities/     # player, enemies, NPCs
│   └── ui/           # menus, HUD, dialogs
│
├── scripts/          # GDScript files, mirroring scenes/ structure
│   ├── world/
│   ├── entities/
│   └── ui/
│
├── assets/
│   ├── textures/
│   ├── audio/
│   │   ├── music/
│   │   └── sfx/
│   └── fonts/
│
├── autoloads/        # Godot singletons — global state, event bus
│   ├── game_manager.gd
│   └── audio_manager.gd
│
├── addons/           # third-party Godot plugins
│
├── extension/        # C++ GDExtension code (if used)
│   ├── src/
│   ├── CMakeLists.txt
│   └── vcpkg.json
│
└── project.godot     # Godot project config — always at root
Plan this before you structure it
  • What are the major entity types? (player, enemies, environment, UI) These become subfolders inside scenes/ and scripts/.
  • What needs to be globally accessible? Autoloads (singletons) — but use them sparingly or they become a dumping ground.
  • What state persists between scenes? Game manager autoload, save file system.
  • Will you write any C++? If yes, set up the extension/ folder and CMake structure from the start — it's harder to retrofit.
  • Are scripts co-located with scenes or separate? Either works, but pick one and stay consistent throughout.

res:// is your project root

Every path in Godot is relative to res://, which maps to your project's root folder. res://scenes/entities/player.tscn is the file at my-game/scenes/entities/player.tscn. Keep this mapping in mind when organizing — what looks tidy in the filesystem should look tidy in the Godot editor too.

Autoloads are global state

Godot autoloads (singletons) are accessible from anywhere in your game. Useful for things like a global event bus or audio manager — but easy to abuse. If too many things live in autoloads, you've created a hidden dependency web. Keep them focused and few.


07

Twitter / Social Graph Visualizer

Python Neo4j FastAPI TypeScript D3.js

This app pulls users and tweets from an API, classifies users by interest, builds a graph of relationships, stores it in a graph database, serves it through an API, and renders it as an interactive node visualization in the browser. It has more layers than most projects — each with a distinct job — and the data flow is strictly one-directional until it reaches the frontend.

Twitter API
ingestion/
processing/
db/ (Neo4j)
api/ (FastAPI)
frontend/ (D3)
directory structure
twitter-viz/
│
├── main.py                   # entry point — starts the pipeline and/or API server
│
├── ingestion/                # pulls raw data from the Twitter API
│   ├── client.py             # auth, session, API connection
│   ├── fetcher.py            # pulls tweets, users, follower graphs
│   └── rate_limiter.py       # handles API rate limits gracefully
│
├── processing/               # cleans data and builds the graph
│   ├── cleaner.py            # deduplicate, normalize, fix bad data
│   ├── classifier.py         # categorize users by topic/interest
│   └── graph_builder.py      # derive node/edge relationships
│
├── db/                       # Neo4j graph database layer
│   ├── client.py             # Neo4j connection and session
│   ├── queries.py            # Cypher read/write operations
│   └── schema.py             # node labels, relationship types, constraints
│
├── api/                      # FastAPI — serves data to the frontend
│   ├── routes.py             # /nodes, /edges, /users/:id endpoints
│   ├── serializers.py        # shape Neo4j results into JSON the frontend expects
│   └── server.py             # FastAPI app instantiation
│
├── frontend/                 # browser visualization
│   ├── src/
│   │   ├── graph.ts          # D3 force-directed graph, node/edge rendering
│   │   ├── filters.ts        # UI controls — filter by topic, time, degree
│   │   └── api.ts            # fetch calls to the FastAPI backend
│   └── index.html
│
├── config/
│   └── .env                  # TWITTER_API_KEY, NEO4J_URI, NEO4J_PASSWORD
│
└── requirements.txt
Plan this before you structure it
  • What is the unit of data? A user? A tweet? A topic cluster? That becomes your primary graph node.
  • What relationships matter? Follows, retweets, shared interests — each becomes a graph edge type in Neo4j.
  • How do you classify users by interest? Keyword matching, ML clustering, hashtag frequency — this is your classifier's job and the hardest part.
  • Does the pipeline run once or continuously? Continuous ingestion needs a scheduler and upsert logic in db/.
  • What does the frontend actually need from the API? Define the JSON shape early — it determines what serializers.py has to produce.
  • How many nodes can D3 render before it gets slow? Plan for pagination or level-of-detail filtering in the API from the start.

Why Neo4j, not Postgres

You're modeling a graph — users connected to other users by relationships. In a relational DB, querying "who are the 3rd-degree connections of this user?" requires expensive joins. In Neo4j, that's a single Cypher query traversing edges. When your primary data structure is a graph, use a graph database.

ingestion never touches the DB directly

The ingestion layer's only job is to pull raw data and hand it off. Processing cleans and classifies it. Only then does it hit the database. This keeps each layer testable in isolation — you can run the classifier against a fixture file without needing a live Twitter API or a running Neo4j instance.


08

Live Audio Visualizer ✦

C++ SFML FFTW CMake vcpkg

Captures audio from a microphone or file in real time, runs a Fast Fourier Transform (FFT) to convert the raw waveform into frequency data, and renders that data as a live animated visualization — frequency bars, waveforms, spectrograms. This project sits at the intersection of signal processing, real-time systems, and graphics, and it maps cleanly onto the simulation/renderer architecture you already know.

The key insight: audio analysis is the simulation layer. The FFT output is just data — a buffer of frequency magnitudes. The renderer reads that buffer and draws it. Neither layer knows anything about the other.

Microphone / file
audio/ (capture)
analysis/ (FFT)
FrequencyData buffer
renderer/ (SFML)
screen
directory structure
audio-viz/
│
├── src/
│   ├── main.cpp              # entry point — loop only
│   │
│   ├── audio/                # capture raw audio samples
│   │   ├── capture.h
│   │   ├── capture.cpp       # mic input via SFML SoundRecorder
│   │   └── sample_buffer.h   # thread-safe ring buffer of raw samples
│   │
│   ├── analysis/             # signal processing — the "simulation" layer
│   │   ├── fft.h
│   │   ├── fft.cpp           # wraps FFTW — converts samples → frequency bins
│   │   ├── spectrum.h
│   │   └── spectrum.cpp      # smoothing, peak detection, band grouping
│   │
│   ├── renderer/             # draws the frequency data — knows nothing about audio
│   │   ├── renderer.h
│   │   ├── renderer.cpp      # owns SFML window, dispatches to draw modes
│   │   ├── bar_visualizer.cpp # classic frequency bar chart
│   │   ├── waveform.cpp      # raw waveform oscilloscope view
│   │   └── spectrogram.cpp   # scrolling time-frequency heat map
│   │
│   └── core/                 # shared types used across layers
│       └── frequency_data.h  # the struct passed from analysis → renderer
│
├── assets/
│   └── fonts/                # for any on-screen labels
│
├── build/                    # CMake output — never commit
│
├── CMakeLists.txt
├── vcpkg.json                # sfml, fftw3
└── .gitignore
Plan this before you structure it
  • What is the data contract between analysis and renderer? Define FrequencyData first — it's the seam between the two halves of the app.
  • How do you handle the audio/render thread mismatch? Audio capture runs on its own thread; the render loop runs on the main thread. You need a thread-safe buffer between them.
  • What FFT window size do you want? Larger = better frequency resolution, worse time resolution. Smaller = snappier response, less detail. This is a design decision, not a code decision.
  • What visualization modes do you want? Each mode is a separate file in renderer/ — plan them upfront so the renderer can switch between them cleanly.
  • Are you capturing live mic input or analyzing a file? Both are valid — but the capture layer changes significantly. Design for one first, then abstract.

core/ — the shared contract

frequency_data.h is the only file both analysis/ and renderer/ know about. It's a plain struct holding the FFT output — magnitudes per frequency bin, maybe smoothed values and peak markers. This is the same pattern as Particle in the particle system: plain data, no behavior, owned by nobody, read by everybody.

Multiple visualizers, one renderer

renderer.cpp owns the window and the loop, but dispatches drawing to whichever visualizer is active — bars, waveform, spectrogram. Each visualizer is a separate file that receives FrequencyData and draws. Adding a new visualization mode means adding one file, not touching the renderer.


Universal Files

Every project

These files appear in almost every project regardless of language or stack. Set them up at the start — retrofitting them is always messier than starting with them.

.env

Secrets and environment-specific config. API keys, database URLs, ports. Never committed to Git. Everyone on the team has their own local copy.

.gitignore

Tells Git what to exclude. Always include: .env, build output folders, dependency directories (node_modules/, build/), editor files (.vscode/ unless shared intentionally).

Dependency manifest

Go: go.mod. Node/TS: package.json. Python: requirements.txt. C++: vcpkg.json. Always committed. Anyone who clones the repo runs one command to reproduce your environment.

README.md

What the project is, how to run it, what environment variables are needed. Future you will thank present you. Even a few sentences is better than nothing.

config/

Code that reads from .env and exposes typed configuration to the rest of the app. Centralizes all environment access so nothing else scatters os.getenv() calls throughout the codebase.

Entry point

main.go, index.ts, main.cpp, main.py. Should be thin — it wires components together and starts the app. Logic in the entry point is a sign something is in the wrong layer.


Common Mistakes

Things that hurt later

Too flat

Everything in root, one giant entry point. Works for a weekend project, impossible to navigate after a month. Split when a file exceeds one clear responsibility.

Too deep

Folders nested four levels down with one file each. Structure should reflect real complexity, not imagined future complexity. Flatten until it hurts, then split.

Naming by type

utils/, helpers/, misc/ are vague containers that accumulate unrelated things. Name by what something does, not the category of thing it is.

Crossing layer boundaries

The renderer importing from simulation logic. The database layer knowing about HTTP. Handlers containing business logic. Each layer should only talk to the one directly below it.

Committing secrets

A .env file in the repo, an API key hardcoded in a source file. Set up .gitignore before your first commit, not after you've already pushed the key.

Logic in main

The entry point grows into the largest file in the project. If main contains logic, find the layer it belongs in and move it. Entry points wire and start — nothing else.