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 dev,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