Tour of your project
apps/, packages/, root config: what every folder is for.
Your my-first-grit/ folder has a lot in it ā easily 100+ files. Don't panic. The shape is predictable and once you learn it here, every Grit project (yours, a teammate's, an open-source one) feels familiar. This tour walks every folder + the files inside, top to bottom, in the order they'll start to matter to you.
Top-level ā what's in the project root
my-first-grit/āāā apps/ # All runnable programs live hereā āāā api/ # Go backend (Gin + GORM)ā āāā web/ # Public Next.js siteā āāā admin/ # Next.js admin panelāāā packages/ā āāā shared/ # TS types + Zod schemas web + admin both importā āāā grit-ui/ # The 100-component Grit UI registry (local copy)āāā tests/ā āāā k6/ # Load tests (smoke, load, stress, spike, soak)āāā e2e/ # Playwright end-to-end browser testsāāā .claude/ # Claude Code agent skill for this projectāāā .github/ # GitHub Actions CI workflowsāāā docker-compose.yml # Local dev infra (Postgres, Redis, MinIO, Mailhog)āāā docker-compose.prod.yml # Production stack (behind your reverse proxy)āāā grit.config.ts # Ports, paths, names ā read by the CLIāāā grit.json # Architecture + frontend + version metadataāāā package.json # Workspace scripts (dev / build / test)āāā pnpm-workspace.yaml # Which folders are workspace membersāāā turbo.json # Build cache + dependency graph for Turborepoāāā postcss.config.mjs # PostCSS config (shared by web + admin)āāā playwright.config.ts # Playwright runner configāāā README.md # Project-specific setup notesāāā .env # Real secrets (gitignored)āāā .env.example # Template ā commit this, edit .env from itāāā .gitignore # Files git ignoresāāā .dockerignore # Files docker ignores during buildāāā .prettierrc # Code formatter rulesāāā .prettierignore # Files prettier skips
Each root file has a single job ā config, secrets, or describes the workspace. There's no "mystery" file at the root: if you see something unfamiliar, it's almost always an editor / tool config (Prettier, Docker, PostCSS) and you can ignore it on day one.
apps/api ā the Go backend (this is where most code lives)
apps/api/āāā cmd/ # Entry points (one main.go per binary)ā āāā server/main.go # The HTTP API process ā run this for the APIā āāā migrate/main.go # CLI: run AutoMigrate against the configured DBā āāā seed/main.go # CLI: seed dev data (admin user, sample blogs)āāā internal/ # All Go packages ā see the table belowāāā Dockerfile # Multi-stage build of cmd/server (alpine runtime)āāā go.mod # Go module declaration + direct depsāāā go.sum # Pinned checksums for every dep
Three commands, three entry points. The interesting work is in internal/, which is laid out as 25+ packages ā one per concern. Here's the rough map (you don't need to memorise it; come back to this table as you build):
| Package | What lives there |
|---|---|
| handlers/ | HTTP handlers ā one file per resource (user.go, blog_handler.go, upload.go, ...). Thin: parse request, call service, format response. |
| services/ | Business logic. Handlers call services; services own the rules and the DB calls. |
| models/ | GORM structs (User, Upload, Blog, ...). One file per model. |
| routes/ | routes.go wires every handler to its URL + middleware. The router map. |
| middleware/ | Auth, CORS, security headers, CSRF, rate-limit, logger, recovery. Each is a small file. |
| config/ | Loads .env into a typed Config struct. Single source of truth for settings. |
| database/ | Opens the Postgres connection, runs AutoMigrate, seeds. GORM lives here. |
| cache/ | Redis cache service + middleware. Set / Get / Delete with TTL. |
| storage/ | S3-compatible client. Works with MinIO locally, R2 / S3 in prod. |
| mail/ | Resend client + HTML templates (welcome, password reset, ...). |
| jobs/ | Background job queue (asynq). Workers + idempotency-aware enqueue helpers. |
| cron/ | Scheduled tasks (nightly cleanup, weekly digest). |
| ai/ | Claude + OpenAI unified client. Streaming chat, embeddings. |
| totp/ | Two-factor auth: TOTP, backup codes, trusted devices. |
| authz/ | Ownership / IDOR helpers ā the "does user X own resource Y?" check. |
| safefetch/ | SSRF-safe HTTP client for any URL the user supplies. |
| paginate/ | Generic pagination params + response shape. |
| respond/ | Consistent JSON envelope helpers: respond.OK, respond.Error. |
| realtime/ | WebSocket hub for live updates (notifications, list refresh). |
| webhooks/ | Signed-payload sender for outbound webhooks (Stripe, Twilio). |
| sync/ | grit sync support ā Go types ā TypeScript on disk. |
| flags/ | Feature flags. Toggle features at runtime without a deploy. |
| audit/ | Activity log writer + the hash-chain integrity check. |
| export/ | PDF + Excel + CSV generation helpers. |
| pdf/ | The PDF rendering primitives export/ uses. |
| docs/ | Auto-generated OpenAPI spec served at /docs via Scalar. |
grit generate resource Customer, it touches THREE files ā internal/models/customer.go, internal/services/customer.go, internal/handlers/customer.go ā plus one line in routes/routes.go. Everything else (cache, storage, mail, auth) is shared infra you can call from any service. Memorise the first 5 packages in the table; the rest you'll discover as you need them.apps/web ā the public Next.js site
apps/web/āāā app/ # App Router routes (page.tsx per route)āāā components/ # Reusable React componentsāāā hooks/ # React Query hooks (use-users.ts, use-blogs.ts, ...)āāā lib/ # api-client.ts, utils.ts, env helpersāāā public/ # Static assets served as-is (favicon, images)āāā __tests__/ # Component / unit tests (Vitest + RTL)āāā next.config.ts # Next.js config (App Router, image domains, ...)āāā tailwind.config.ts # Tailwind theme + tokens (matches admin)āāā postcss.config.js # PostCSS pipeline (Tailwind + autoprefixer)āāā tsconfig.json # TS compiler config (path aliases, strict mode)āāā package.json # Web-specific deps + scriptsāāā vitest.config.ts # Test runnerāāā vitest.setup.ts # Test setup (jest-dom matchers, fake timers)āāā Dockerfile # Multi-stage build ā nginx-served bundle on :3000
Standard Next.js 14+ App Router. The two folders to memorise: app/ for routes (each page.tsx is a URL) and hooks/ for React Query ā every API call goes through a hook, not a raw fetch inside a component.
apps/admin ā the Filament-style admin panel
apps/admin/āāā app/ # Routes (auth, dashboard, system pages)āāā resources/ # ā defineResource() per model ā auto-CRUD pagesāāā components/ # Admin-specific layout + widgetsāāā hooks/ # React Query hooks (admin endpoints)āāā lib/ # API client (uses cookie auth + CSRF header)āāā public/ # Static assetsāāā __tests__/ # Vitest testsāāā next.config.ts # Next.js configāāā tailwind.config.ts # Tailwind theme ā same tokens as apps/webāāā postcss.config.jsāāā tsconfig.jsonāāā package.json # Admin-specific depsāāā vitest.config.tsāāā vitest.setup.tsāāā Dockerfile
Same skeleton as apps/web. The unique folder is resources/ā each file is a defineResource() call that auto-builds list + create + edit + delete pages from a model. We'll spend a full chapter here in the Web (Next.js) course.
packages/shared ā the type bridge
packages/shared/āāā types/ # TypeScript types (generated from Go via grit sync)āāā schemas/ # Zod schemas (also generated, used for form validation)āāā constants/ # Shared route paths, role names, enumsāāā tsconfig.jsonāāā package.json # Published to the workspace as @<project>/shared
When you run grit sync, Go structs in apps/api/internal/models/*.go become TypeScript types in packages/shared/types/. Both apps/web and apps/admin import these ā so a wrong field name in your React code is a compile error, not a 3am bug.
packages/grit-ui ā the 100-component library
packages/grit-ui/āāā registry.json # Master index ā every component's name + pathāāā registry/ # Per-component JSON (metadata) + TSX (source)āāā package.json
A shadcn-compatible local copy of the Grit UI registry (marketing sections, auth screens, SaaS dashboards, e-commerce, layouts). You copy components into apps/web/components/ or apps/admin/components/ as you need them; the originals stay here as reference.
tests/k6 ā load tests
tests/k6/āāā smoke.js # 10 VUs / 30s ā sanity check on every PRāāā average-load.js # Expected production traffic, 5-10 mināāā stress.js # Ramp until the API breaksāāā spike.js # Sudden 10Ć surge (marketing email scenario)āāā soak.js # Moderate load for hours ā catches leaksāāā breakpoint.js # Find the cliffāāā lib/ # Reused: auth helpers, thresholdsāāā README.md # How to run each one + thresholds explained
The K6 Load Testing course walks all of these. They're ready to run today ā just point them at your local API.
e2e ā Playwright browser tests
e2e/āāā auth.spec.ts # Register ā login ā logout ā forgot passwordāāā admin.spec.ts # Admin login ā DataTable ā resource CRUD
Headless browser tests of the real frontends against the real API. Run with pnpm test:e2e. Adds ~30 seconds to your CI but catches whole categories of bugs unit tests miss.
.claude ā Claude Code agent skill
.claude/āāā skills/grit/SKILL.md # Project-specific instructions for Claude Code
If you use Claude Code (Anthropic's CLI agent), it reads this file to learn the conventions of THIS project. Auto-generated; you can edit it to teach Claude your team's preferences.
.github ā CI workflows
.github/āāā workflows/ # GitHub Actions YAML ā runs on every push/PRāāā ci.yml # Go tests + race + coverage + cross-platform buildāāā release.yml # Tag-triggered binary release
The root config files ā what each one is for
grit.config.tsā ports, paths, project name. The CLI reads this when you rungrit start,grit deploy, etc.grit.jsonā architecture mode + frontend + the Grit CLI version that scaffolded the project. Used bygrit upgradeto know what to regenerate.package.json(root) ā workspace-level scripts:dev,build,test,lint. These are aliases that Turbo dispatches into each app.pnpm-workspace.yamlā tells pnpm which folders are workspace members. Adding a new app means adding it here.turbo.jsonā Turborepo's pipeline. Says "beforebuild, build the things this depends on, and cache the result".docker-compose.ymlā local infrastructure (Postgres, Redis, MinIO, Mailhog). Ports are bound to127.0.0.1only ā your laptop, nothing on the LAN can reach them.docker-compose.prod.ymlā production stack behind a reverse proxy. None of the services bind to a public port; traffic arrives via Traefik / Caddy / nginx / Dokploy..envā real secrets, gitignored. Generated with crypto- random JWT + session secrets..env.exampleā same keys as.envbut with placeholder values. Commit this; teammates copy it to.envand fill in their own secrets..gitignore,.dockerignoreā what git and docker should skip. Standard defaults..prettierrc,.prettierignoreā TS / TSX formatting rules so the team is consistent.postcss.config.mjsā root PostCSS config (Tailwind plugin). Inherited by bothapps/webandapps/admin.playwright.config.tsā test runner config fore2e/. Tells Playwright which browsers to test, where the dev server lives, where to put trace files.README.mdā your project-specific notes (boot order, quirks, deploy targets). Edit this as you go.
Quick check
Try it
Open your my-first-grit in your editor and answer in notes.md:
- How many files are under
apps/api/internal/handlers/? - Open
apps/api/internal/handlers/user.go. How many exported functions does it have? Does it import a service? - Open
apps/api/internal/routes/routes.goand find the line whereuserroutes are mounted. What URL prefix is used? - Open
packages/shared/types/. Pick any.tsfile. Does the comment at the top say anything about being generated?
The point: practice navigating before the next lesson, where we boot everything up.
What's next
You know the layout. Next lesson is a Docker primer ā many learners get stuck on the "run docker compose up" step because Docker itself feels foreign. We'll fix that before actually starting the dev servers in lesson 4.
Spot a typo? Have an idea?
Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled, suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.
Suggest an improvement on GitHub