Folder conventions
internal/, packages/shared, apps/ — the rules and the rationale.
Grit has one place for everything. That sounds like a constraint — it's actually a superpower. Every team member, every AI agent, every future-you knows where to look without thinking. This lesson covers the rules.
The four big places
- apps/api/cmd/ — entry points (just
server/main.gousually) - apps/api/internal/ — everything that's not exported. The actual code lives here.
- apps/web/ + apps/admin/ — Next.js apps. Standard App Router shape.
- packages/shared/ — Zod + TS types. Imported by web and admin.
internal/ — the seven sub-folders that matter
internal/├── config/ Loads .env into a typed Config struct.├── database/ Postgres connection + AutoMigrate.├── handlers/ HTTP handlers — thin, one per resource.├── middleware/ auth, CORS, security, request ID, etc.├── models/ GORM struct definitions.├── routes/ routes.go — mounts every handler on a route.└── services/ Business logic — called by handlers.
The flow of a request
HTTP request→ middleware (auth, CORS, security)→ routes/routes.go picks the handler→ handlers/x.go parses input, calls services→ services/x.go does the work, calls models→ models/x.go is the GORM struct used to read/write DB→ response shaped by handler, sent back
packages/shared/ — the type bridge
packages/shared/├── src/│ ├── schemas/ Zod schemas (used to validate API calls)│ ├── types/ TS types (generated by grit sync from Go structs)│ └── constants/ Route constants, enums, shared values.└── package.json Imported as @workspace/shared by web + admin
What does NOT go in internal/
- Tests — they live alongside the file they test:
handlers/user.go→handlers/user_test.go. - Migrations — Grit uses GORM AutoMigrate by default. No
internal/migrations/folder. - Static assets — frontend assets live in
apps/web/public/orapps/admin/public/.
Quick check
Try it
You need to add a NotificationService that sends push notifications when a user's order ships. Decide where each piece goes — write it in notes.md:
- The struct + methods that talk to Expo Push?
- The DB row that tracks "was this notification sent?"
- The HTTP endpoint to trigger a test push from the admin panel?
- The TypeScript type for the admin form's payload?
What's next
Folders sorted. Next — naming. snake_case for Go files, kebab-case for TS, plural for routes. Same logic: one rule, applied everywhere.
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