Your First Mobile App
In this course, you will scaffold a mobile project with Grit, set up your development environment, run the app on a device or emulator, and understand how the Go API and Expo app work together. By the end, you will have a working mobile app connected to a live backend.
What is Mobile Development with Grit?
Grit scaffolds a monorepo that contains both a Go API and an Expo React Native app. You write React components that compile to native iOS and Android code. The Go API is exactly the same as in web projects — same routes, same services, same database. Only the frontend is different.
- • The Go API runs on your server — same as a web project
- • The Expo app runs on a phone (or emulator) and calls the API over HTTP
- • Shared types keep the API and app in sync
- • One codebase, two platforms (iOS + Android)
Prerequisites
You need the same tools as a web project, plus a few mobile-specific ones:
| Tool | What it does | Check |
|---|---|---|
| Go 1.21+ | Runs the backend API | go version |
| Node.js 18+ | Runs the Expo dev server | node --version |
| pnpm 8+ | Installs JavaScript packages | pnpm --version |
| Docker | Runs PostgreSQL, Redis, MinIO | docker --version |
| Expo Go app | Runs your app on a physical phone | App Store / Play Store |
Challenge: Install Expo Go
Install the Expo Go app on your phone from the App Store (iOS) or Play Store (Android). Open it and confirm it launches successfully.
Challenge: Check Your Tools
Open your terminal and run all four check commands from the table above. Confirm each tool is installed and returns a version number.
Scaffold a Mobile Project
Use the --mobile flag to scaffold a project with an Expo app instead of (or alongside) a web frontend:
grit new fitness --mobileThis creates a monorepo with the following structure:
fitness/
├── apps/
│ ├── api/ ← Go backend (same as web)
│ └── expo/ ← React Native app (Expo)
├── packages/
│ └── shared/ ← Shared types + schemas
├── docker-compose.yml
└── turbo.jsonThe apps/api/ directory is identical to a web project — same Go code, same routes, same services. The only difference is apps/expo/ replacingapps/web/.
app/ directory become navigable screens in your app.Challenge: Scaffold a Mobile Project
Run grit new fitness --mobile. Explore the folder structure. How many directories are inside apps/? What's inside packages/shared/?
Start the API
The API is a standard Grit Go backend. Start the infrastructure services first, then run the API server:
# Start PostgreSQL, Redis, MinIO
docker compose up -d
# Run the Go API
cd apps/api && go run cmd/server/main.goThe API will start on http://localhost:8080. You can verify it's running by opening that URL in your browser — you should see a JSON health check response.
Challenge: Start the API
Start the infrastructure with docker compose up -d, then run the API. Open http://localhost:8080 in your browser. What JSON response do you see?
Start Expo
With the API running, open a new terminal and start the Expo dev server:
cd apps/expo && npx expo startExpo will display a QR code in the terminal. You have three options:
- Physical phone: Scan the QR code with Expo Go (Android) or your camera app (iOS)
- iOS Simulator: Press
iin the terminal (macOS only, requires Xcode) - Android Emulator: Press
ain the terminal (requires Android Studio)
Challenge: Run Expo
Start Expo and open the app on your phone or emulator. You should see the default home screen. Try editing apps/expo/app/index.tsx — does the change appear immediately?
Project Structure
The Expo app follows the same conventions as a Next.js project. Here's what's insideapps/expo/:
apps/expo/
├── app/ ← Screens (Expo Router)
│ ├── _layout.tsx ← Root layout (wraps all screens)
│ ├── index.tsx ← Home screen
│ ├── login.tsx ← Login screen
│ ├── register.tsx ← Register screen
│ └── (tabs)/ ← Tab navigation group
│ ├── _layout.tsx ← Tab bar configuration
│ ├── index.tsx ← Home tab
│ └── profile.tsx ← Profile tab
├── components/ ← Reusable UI components
├── hooks/ ← Custom hooks (useAuth, etc.)
├── lib/ ← API client, utilities
└── package.jsonKey Expo Router conventions:
- •
_layout.tsx— Wraps child screens. Defines navigation structure (stack, tabs). - •
index.tsx— The default screen for a directory (like/in a URL). - •
(tabs)/— A route group. The parentheses mean it doesn't appear in the URL. - • Any
.tsxfile inapp/becomes a screen automatically.
Challenge: Explore the Structure
Open apps/expo/app/. List all the files. Which file is the root layout? What does the (tabs)/ directory contain?
Challenge: Understand the Layout
Open apps/expo/app/_layout.tsx. What component does it use to wrap the app? Does it set up a Stack navigator or a Tab navigator at the root level?
Shared Types
The packages/shared/ directory is used by both the Go API and the Expo app. It contains Zod schemas and TypeScript types that keep the frontend and backend in sync.
packages/shared/
├── types/
│ ├── user.ts ← User type + schemas
│ ├── auth.ts ← Login/register schemas
│ └── api.ts ← API response types
├── schemas/
│ └── index.ts ← Zod validation schemas
└── constants/
└── routes.ts ← API route constantsWhen you use grit generate resource to add a new API resource, the TypeScript types are automatically generated in packages/shared/. Your mobile app gets them for free — no manual syncing required.
Challenge: Explore Shared Types
Open packages/shared/types/. What types are defined? Compare them to what the API returns at /api/auth/login. Are they the same?
The API Client
The mobile app calls the Go API using a configured HTTP client. Here's how a typical API call looks:
// lib/api.ts
const API_URL = "http://localhost:8080"
export async function login(email: string, password: string) {
const response = await fetch(API_URL + "/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
})
return response.json()
}localhost won't work — your phone can't reach your computer's localhost. You need to use your computer's local IP address instead (e.g., http://192.168.1.42:8080). Expo shows your local IP in the terminal output when you run npx expo start.http://10.0.2.2:8080 to reach the host machine's localhost. This is a special alias built into the Android emulator.Challenge: Find the API Client
Find the API client file in the Expo app (check lib/ or hooks/). What base URL does it use? What headers does it set?
Challenge: Test the Connection
With the API running, open the mobile app and try to register a new user. Check the API logs in your terminal — do you see the incoming request?
Summary
Here's what you learned in this course:
- Grit scaffolds a monorepo with a Go API and an Expo React Native app
- The Go API is identical to web projects — same code, same routes
- Expo Router provides file-based navigation (like Next.js for mobile)
- Shared types keep the API and mobile app in sync
- The API client connects the mobile app to the Go backend over HTTP
Challenge: Register a User
Using the mobile app, register a new user with an email and password. Then log in with those credentials. Does the app navigate to the home screen?
Challenge: End-to-End Test
Create a blog post via the API docs at http://localhost:8080/swagger. Then open the mobile app and navigate to the content screen. Does the new post appear? Pull down to refresh if needed.
Enjoying the course?
Help us grow — star us on GitHub, subscribe on YouTube, and follow on LinkedIn.