Your First App
Build a contact manager from scratch using Grit. You will create two resources — Group and Contact — generate them with a single CLI command each, and explore the admin panel, GORM Studio, and API docs that Grit gives you for free.
Prerequisites
- ✓Go 1.21+ installed
- ✓Node.js 18+ and pnpm installed
- ✓Docker and Docker Compose installed
- ✓Grit CLI installed globally (go install github.com/MUKE-coder/grit/v2/cmd/grit@latest)
Create the project
Scaffold a new Grit monorepo called contact-app. This creates the Go API, Next.js web app, admin panel, shared package, and Docker configuration in one shot.
Grit prints an ASCII art logo, creates the folder structure, initializes go.mod, and prints the next steps. Your project is ready.
Install dependencies
Install all Node.js packages for the web app, admin panel, and shared package. Grit uses pnpm workspaces so everything installs from the project root.
Start Docker services
Spin up PostgreSQL, Redis, MinIO (local S3), and Mailhog. These run in the background and persist data across restarts.
Check for port conflicts
Docker needs port 5432 (PostgreSQL), 6379 (Redis), 9000/9001 (MinIO), and 1025/8025 (Mailhog). If any of these are already in use, you will see an error.
The most common conflict is port 5432 — if you have PostgreSQL installed locally it's already listening there. Check what's using the port:
You have three options:
- Stop the local service — run
net stop postgresql-x64-16(Windows) orbrew services stop postgresql@16(macOS). This frees the port so Docker can use it. - Uninstall local PostgreSQL — if you only use Docker for databases, remove the local installation entirely to avoid future conflicts.
- Change the port — update
docker-compose.ymlto map a different host port:ports: "5433:5432"
Then updateDATABASE_URLin.envto use port5433.
Run docker compose ps to verify all four services are running and healthy.
Run migrations
Create the database tables. Grit uses GORM AutoMigrate, which reads your Go models and creates/updates the corresponding PostgreSQL tables automatically.
Seed the database
Populate the database with an admin user and sample blog data. The default admin credentials are admin@example.com / password.
Start the API server
Open a terminal and start the Go API. This compiles and runs the server on http://localhost:8080.
Start the frontend
Open a second terminal and start both Next.js apps (web + admin) using Turborepo. The web app runs on localhost:3000 and the admin panel on localhost:3001.
Create a user and log in
Open http://localhost:3000 in your browser. You will see the login page. Click Register to create a new account, then log in.
For the admin panel at http://localhost:3001, use the seeded admin credentials:
After logging in to the admin panel, you will see the dashboard with stats cards, a chart, and the Users resource in the sidebar. The blog example is also available — click Posts in the sidebar to see the seeded blog data.
Explore the built-in tools
Grit ships with powerful development tools out of the box. Open these URLs while your server is running:
http://localhost:8080/studio— GORM Studio — visual database browser to inspect tables, run queries, and see relationshipshttp://localhost:8080/docs— API Documentation — auto-generated interactive docs for every endpointhttp://localhost:8080/sentinel/ui— Sentinel — security dashboard with WAF, rate limiting, and threat monitoringhttp://localhost:8080/pulse— Pulse — observability dashboard with request tracing, database monitoring, and runtime metricshttp://localhost:8025— Mailhog — catches all emails sent during developmenthttp://localhost:9001— MinIO Console — browse uploaded files (login: minioadmin / minioadmin)
Generate the Group resource
A contact belongs to a group (e.g. Friends, Family, Work). Generate the Group resource first since Contact will reference it. The slug:slug:name field automatically generates a URL-friendly slug from the group name.
The generator creates these files:
apps/api/internal/models/group.go # GORM modelapps/api/internal/handlers/group.go # CRUD handlerapps/api/internal/services/group.go # Business logicpackages/shared/schemas/group.ts # Zod validationpackages/shared/types/group.ts # TypeScript typesapps/web/hooks/use-groups.ts # React Query hooks (web)apps/admin/hooks/use-groups.ts # React Query hooks (admin)apps/admin/app/resources/groups/page.tsx # Admin pageapps/admin/resources/groups.ts # Resource definition
Here is the generated Go model:
package modelsimport ("time""gorm.io/gorm")type Group struct {ID uint `gorm:"primarykey" json:"id"`Name string `gorm:"size:255;not null" json:"name" binding:"required"`Slug string `gorm:"size:255;uniqueIndex;not null" json:"slug"`CreatedAt time.Time `json:"created_at"`UpdatedAt time.Time `json:"updated_at"`DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`}
Generate the Contact resource
Now generate the Contact resource with all the fields: name, email, phone, country, image (as a string array for multiple photos), and a belongs_to relationship to Group.
The generated Contact model includes a GroupID foreign key and a Group relation. The Image field is a JSON array that stores multiple image URLs:
package modelsimport ("time""gorm.io/datatypes""gorm.io/gorm")type Contact struct {ID uint `gorm:"primarykey" json:"id"`Name string `gorm:"size:255;not null" json:"name" binding:"required"`Email string `gorm:"size:255;not null" json:"email" binding:"required"`Phone string `gorm:"size:255;not null" json:"phone" binding:"required"`Country string `gorm:"size:255;not null" json:"country" binding:"required"`Image datatypes.JSONSlice[string] `gorm:"type:json" json:"image"`GroupID uint `gorm:"index;not null" json:"group_id" binding:"required"`Group Group `gorm:"foreignKey:GroupID" json:"group,omitempty"`CreatedAt time.Time `json:"created_at"`UpdatedAt time.Time `json:"updated_at"`DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`}
Note: The string_array field type uses datatypes.JSONSlice[string] from gorm.io/datatypes. The generator automatically runs go get to install this dependency for you.
Rebuild the API
The generator injected new code into your Go API — models, handlers, routes, and GORM Studio registration. Build the API to make sure everything compiles:
If the build succeeds with no output, everything is wired correctly. If you see errors, check that both group.go and contact.go models exist in apps/api/internal/models/.
Restart and check the admin panel
Stop the API server (Ctrl+C in terminal 1) and restart it. GORM will automatically create the groups and contacts tables in PostgreSQL.
Open the admin panel at http://localhost:3001. You will see Groups and Contacts in the sidebar alongside Users and Posts. The admin panel reads from the resource registry at runtime — no manual sidebar configuration needed.
Create groups and contacts
In the admin panel, go to Groups and create a few groups:
Then go to Contacts and create some contacts. When you click Create, the form will show a dropdown to select the group — this is the belongs_to relationship in action. Fill in the name, email, phone, country, and optionally upload images.
Tip: Open GORM Studio at http://localhost:8080/studio to see your groups and contacts in the database. You can browse the tables, see the relationships, and even run raw SQL queries.
Explore the API
Every resource you generate gets a full REST API. Try these endpoints in your browser or with a tool like Postman:
GET /api/groups— List all groups with paginationGET /api/groups/:id— Get a single groupPOST /api/groups— Create a new groupGET /api/contacts— List all contacts with paginationGET /api/contacts/:id— Get a single contact with its groupPOST /api/contacts— Create a new contact
All endpoints support ?page=1&page_size=20 for pagination, ?sort=name&order=asc for sorting, and ?search=john for full-text search. Check the interactive API docs at http://localhost:8080/docs for the complete reference.
What you've built
- ✓A full-stack contact manager with Go API and Next.js frontend
- ✓Group and Contact resources generated with a single CLI command each
- ✓A BelongsTo relationship between Contacts and Groups
- ✓An admin panel with sortable tables, forms, and group selection
- ✓A JSON array field (image) for storing multiple images per contact
- ✓REST API with pagination, sorting, and search out of the box
- ✓GORM Studio for visual database browsing
- ✓Auto-generated API documentation
- ✓Docker-based PostgreSQL, Redis, MinIO, and Mailhog running locally
Next steps
Now that you have a working contact manager, here are some ideas to extend it:
- Add tags — generate a
Tagresource and create a many-to-many relationship so contacts can have multiple tags like "VIP" or "Newsletter". - Role-based access — use
grit add role MANAGERand the--rolesflag on generate to restrict who can manage contacts. - Export contacts — add a custom Go handler that exports contacts as a CSV file.
- Send emails — use the built-in Resend email service to send a welcome email when a new contact is created.
- Build the web frontend — create a public contacts page in the web app using the generated React Query hooks.