Architecture Modes
Single Architecture: One Binary
One Go binary serves both the API and the frontend. No monorepo tooling, no separate deployments. Build it, ship one file, done.
Overview
The single architecture is the most different from the other Grit modes. Instead of a Turborepo monorepo with separate apps, you get a flat project where a single Go binary serves both the REST API and the compiled React frontend using go:embed. Think of it like Laravel or an embedded Next.js app -- one deployment unit that handles everything.
There is no Turborepo, no pnpm-workspace.yaml, no turbo.json. The Go module path is just myapp (not myapp/apps/api). Schemas and types live directly in frontend/src/ -- no shared package needed.
Scaffold command
grit new myapp --single --vite
Key Characteristics
| Property | Single Architecture |
|---|---|
| Binary | One Go binary serves API + frontend |
| Monorepo | None -- flat project, no Turborepo, no pnpm workspaces |
| Module path | myapp |
| Frontend | React + Vite + TanStack Router (embedded via go:embed) |
| Shared types | Inline in frontend/src/ (no packages/shared) |
| Deployment | Upload one binary + .env file |
Full Folder Structure
The entire project is flat. Go code lives in internal/, React code lives in frontend/, and main.go sits at the root.
myapp/├── main.go # Entry point with go:embed frontend/dist/*├── go.mod # Module: myapp (not myapp/apps/api)├── go.sum├── .env├── .env.example├── .gitignore├── .prettierrc├── .prettierignore├── docker-compose.yml # PostgreSQL, Redis, MinIO, Mailhog├── docker-compose.prod.yml├── grit.json # architecture: "single", frontend: "tanstack"├── Makefile # make dev, make build, make deploy├── .claude/skills/grit/│ ├── SKILL.md # Tailored to single architecture│ └── reference.md├── internal/ # ALL Go backend code│ ├── config/config.go│ ├── database/db.go│ ├── models/ # // grit:models│ ├── handlers/│ ├── services/│ ├── middleware/│ ├── routes/routes.go # // grit:handlers, grit:routes:*│ ├── mail/│ ├── storage/│ ├── jobs/│ ├── cache/│ ├── ai/│ └── auth/totp.go└── frontend/ # React + Vite + TanStack Router├── package.json├── vite.config.ts # Proxy /api → localhost:8080├── tailwind.config.ts├── tsconfig.json├── index.html└── src/├── main.tsx├── routes/ # TanStack Router file-based routes│ ├── __root.tsx│ ├── index.tsx│ └── ...├── components/├── hooks/├── lib/├── schemas/ # Zod schemas (inline, not shared package)└── types/ # TypeScript types (inline)
Directory Breakdown
main.go -- Entry Point
The root main.go file is where everything starts. It uses Go's embed package to bundle the compiled frontend into the Go binary. In production, this means you get a single executable that can serve both the API and the static frontend files.
package mainimport ("embed""io/fs""net/http""myapp/internal/config""myapp/internal/database""myapp/internal/routes")//go:embed frontend/dist/*var frontendFS embed.FSfunc main() {cfg := config.Load()db := database.Connect(cfg)// Serve API routesr := routes.Setup(db, cfg)// Serve embedded frontenddist, _ := fs.Sub(frontendFS, "frontend/dist")r.NoRoute(gin.WrapH(http.FileServer(http.FS(dist))))r.Run(":" + cfg.Port)}
The //go:embed frontend/dist/* directive tells the Go compiler to include the entire frontend/dist/ directory inside the compiled binary. The NoRoute handler serves the frontend for any path that doesn't match an API route.
internal/ -- Go Backend
Identical structure to the other architectures. Contains config, database connection, models, handlers, services, middleware, mail, storage, jobs, cache, and AI integration. The only difference is import paths: you import "myapp/internal/models" instead of "myapp/apps/api/internal/models". The routes/routes.go file contains the same code markers (// grit:handlers, // grit:routes:*) for code generation.
frontend/ -- React SPA
A standard React + Vite + TanStack Router application. File-based routing lives in src/routes/. Unlike the triple or double architecture, there is no packages/shared/ directory. Zod schemas live in src/schemas/ and TypeScript types in src/types/ -- everything is self-contained within the frontend directory.
grit.json -- Project Config
Tells the Grit CLI which architecture this project uses. Code generation reads this file to determine where to place generated files and which templates to use.
{"architecture": "single","frontend": "tanstack","go_module": "myapp"}
Data Flow
The data flow differs between development and production. In development, you run two processes: Go on port 8080 and Vite on port 5173. Vite proxies API requests to Go. In production, there is only one process: the Go binary serves everything.
Production
Browser → Go binary (:8080)├── /api/* → Gin router → handlers → services → database└── everything else → serves frontend/dist/ (HTML, JS, CSS)
Development
Browser → Vite dev server (:5173)├── /api/* → proxied to Go (:8080) → handlers → services → database└── other → Vite HMR (instant refresh)
The Vite proxy is configured in vite.config.ts to forward any request starting with /api to the Go server running on port 8080.
Development Workflow
During development, you run two processes in separate terminals. The Makefile provides a convenience command that runs both at once.
Terminal 1: Go API
# Start the Go API with hot reloadair# or without air:go run main.go
Terminal 2: Vite Frontend
cd frontend && pnpm dev
Or use the Makefile
# Runs both Go and Vite concurrentlymake dev
Vite Proxy Config
The Vite config proxies API requests so the frontend can call /api/users without worrying about CORS or port differences during development.
import { defineConfig } from 'vite'import react from '@vitejs/plugin-react'import { TanStackRouterVite } from '@tanstack/router-plugin/vite'export default defineConfig({plugins: [TanStackRouterVite(), react()],server: {port: 5173,proxy: {'/api': {target: 'http://localhost:8080',changeOrigin: true,},},},})
Production Build
Building for production is a two-step process: build the frontend, then build the Go binary. The Go compiler embeds the compiled frontend assets into the binary at compile time.
# Step 1: Build the frontendcd frontend && pnpm build# This creates frontend/dist/ with optimized HTML, JS, and CSS# Step 2: Build the Go binarycd .. && go build -o myapp main.go# The binary now contains the embedded frontend# Step 3: Deployscp myapp server:/opt/myapp/scp .env server:/opt/myapp/# That's it. One binary + one .env file.
No Node.js runtime needed on the production server. No npm install, no pnpm, no build tools. Just the binary and your environment variables.
Code Generation
When you run grit generate resource in a single-architecture project, the CLI creates files in different locations compared to the monorepo architectures.
| Generated File | Location |
|---|---|
| Go model | internal/models/post.go |
| Go service | internal/services/post_service.go |
| Go handler | internal/handlers/post_handler.go |
| Route injection | internal/routes/routes.go |
| Zod schema | frontend/src/schemas/post.ts |
| TypeScript types | frontend/src/types/post.ts |
| React Query hooks | frontend/src/hooks/use-posts.ts |
Notice that schemas and types go into frontend/src/ directly, not into a packages/shared/ directory. There is no shared package in the single architecture.
Deployment
Deploying a single-architecture app is as simple as it gets. You only need to transfer two files to your server: the compiled binary and your .env file.
Docker
FROM node:20-alpine AS frontendWORKDIR /app/frontendCOPY frontend/package.json frontend/pnpm-lock.yaml ./RUN corepack enable && pnpm install --frozen-lockfileCOPY frontend/ ./RUN pnpm buildFROM golang:1.21-alpine AS backendWORKDIR /appCOPY go.mod go.sum ./RUN go mod downloadCOPY . .COPY --from=frontend /app/frontend/dist ./frontend/distRUN CGO_ENABLED=0 go build -o server main.goFROM alpine:3.19WORKDIR /appCOPY --from=backend /app/server .EXPOSE 8080CMD ["./server"]
Direct Deploy (no Docker)
# Build locally for your server's OS/archGOOS=linux GOARCH=amd64 go build -o myapp main.go# Transfer and runscp myapp .env yourserver:/opt/myapp/ssh yourserver 'cd /opt/myapp && ./myapp'
When to Choose Single
Good fit
- -Solo developers who want the simplest deploy story
- -Laravel or Rails developers who like one-app-does-everything
- -Internal tools and admin dashboards
- -Microservices where each service has its own UI
- -Apps where SSR and SEO are not critical (SPA is fine)
Not ideal for
- -SEO-heavy public sites that need server-side rendering
- -Large teams that need separate frontend and backend deploys
- -Projects that need a separate admin panel (use triple instead)
- -Apps that share types with multiple frontend clients
Example Project
The Job Portal example built with the single architecture demonstrates all the patterns described above: go:embed, Vite proxy, TanStack Router, and production Dockerfile.
View Job Portal (Single + Vite) on GitHub →