Tour of your project

apps/, packages/, root config — what every folder is for.

8 mineasy

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/
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/
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):

PackageWhat 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.
The mental model: when you generate a resource with 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/
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/
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/
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/
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/
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/
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/
.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/
.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 run grit dev, grit deploy, etc.
  • grit.json — architecture mode + frontend + the Grit CLI version that scaffolded the project. Used by grit upgrade to 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 "before build, build the things this depends on, and cache the result".
  • docker-compose.yml — local infrastructure (Postgres, Redis, MinIO, Mailhog). Ports are bound to 127.0.0.1 only — 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 .env but with placeholder values. Commit this; teammates copy it to .env and 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 both apps/web and apps/admin.
  • playwright.config.ts — test runner config for e2e/. 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.
You don't need to read all of this today. Bookmark this lesson. Come back when you hit a folder you don't recognise. By the time you ship your second Grit project the layout is muscle memory.

Quick check

A teammate asks: 'Where do I put the business logic for handling refunds?' What's your answer?

Try it

Open your my-first-grit in your editor and answer in notes.md:

  1. How many files are under apps/api/internal/handlers/?
  2. Open apps/api/internal/handlers/user.go. How many exported functions does it have? Does it import a service?
  3. Open apps/api/internal/routes/routes.go and find the line where user routes are mounted. What URL prefix is used?
  4. Open packages/shared/types/. Pick any .ts file. 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