A full-stack mobile AI chat app built on the notJust.dev YouTube tutorial. It uses Expo Router (file-based routing), Clerk for authentication, an Expo Router API route as a backend proxy, the OpenAI API for text generation, zustand + expo-sqlite for persisted state, and react-native-keyboard-controller / react-native-enriched-markdown for the chat UI.
- Node.js 20+ and npm
- Xcode (iOS) or Android Studio (Android) — Expo Go isn't enough here, this project uses native modules
- A Clerk account → ntjst.dev/tu4Abaw
- An OpenAI API key → platform.openai.com
# 1. Clone
git clone git@github.com:notJust-dev/AIChat.git
cd AIChat
# 2. Install
npm install
# 3. Configure env
cp .env.example .env.local
# then open .env.local and fill in:
# EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
# CLERK_SECRET_KEY=sk_test_...
# OPENAI_API_KEY=sk-...
# 4. Run on a simulator/device
npm run ios # iOS Simulator (macOS only)
npm run android # Android Emulator
npm run web # Web browser
# Or start Metro and pick a target from the menu:
npm startNote on Expo Go: this app uses native modules (
react-native-enriched-markdown,react-native-keyboard-controller,expo-sqlite, Clerk) so it must run in a dev client, not Expo Go. The commands above build a dev client for you the first time.
If you ever change app.json or add a config-plugin dependency, restart Metro with the cache cleared:
npx expo start --clear| Variable | Where it's used | Notes |
|---|---|---|
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY |
Client (src/app/_layout.tsx) |
EXPO_PUBLIC_ prefix means it ships in the JS bundle — must be the publishable key, never the secret one. |
CLERK_SECRET_KEY |
Clerk CLI / Backend API | Server-side only. |
OPENAI_API_KEY |
API route (src/app/api/chat+api.ts) |
Server-side only — never prefix with EXPO_PUBLIC_. |
This repo's commit history matches the steps in the video. Each commit is a single, self-contained piece of work, so you can check out any commit to see the project at that exact point.
git log --oneline| Commit (most recent first) | What it adds |
|---|---|
update claude.md |
Project guidance for AI tools |
scroll to end |
Auto-scroll the chat to the latest message during streaming |
streaming |
Token-by-token streaming from OpenAI to the UI |
markdown |
Render messages with react-native-enriched-markdown |
integrate openai |
Real OpenAI completions in the API route |
setup api routes |
Expo Router API route returning a dummy response |
list of user chats in the drawer |
Drawer sidebar showing all chats from the store |
global chat state with zustand |
Persisted chat store (zustand + expo-sqlite) |
chat ui |
Message list, message input, keyboard handling |
navigation structure |
Stack.Protected + protected group + chat drawer |
clerk auth with prebuilt components |
Sign-in with Clerk's <AuthView /> |
clerk skills / expo skills / claude.md |
Tooling setup |
reset project |
Blank slate from the Expo template |
To inspect the project at any tutorial step:
# read-only look — detaches HEAD
git checkout <commit-sha>
# return to the latest
git checkout mainTo see exactly what changed in a step:
git show <commit-sha> # diff + commit message
git diff <prev-sha>..<sha> # diff between two stepsIf you want to start coding from a commit (say, after chat ui and write streaming yourself):
git checkout -b my-attempt <commit-sha-of-chat-ui>
npm install # the dependency list may differ at older commits
npx expo start --clearsrc/
app/ # Expo Router routes
_layout.tsx # Providers + Stack.Protected auth gate
sign-in.tsx # Clerk AuthView
(protected)/
_layout.tsx
index.tsx # Redirect to /chat/new
chat/
_layout.tsx # Drawer navigator
[id].tsx # Chat screen (id can be "new" or a UUID)
api/
chat+api.ts # POST /api/chat — OpenAI streaming proxy
components/
DrawerContent.tsx # Custom drawer with chat list
chat/
MessageList.tsx
MessageListItem.tsx
MessageInput.tsx
store/
chatStore.ts # Zustand store, persisted to expo-sqlite
lib/
api.ts # apiUrl() helper for native/web fetch
See CLAUDE.md for deeper architecture notes (routing, drawer, store, streaming, keyboard handling, env vars).
npm start # Metro / Expo dev server
npm run ios # Run on iOS Simulator
npm run android # Run on Android Emulator
npm run web # Run on web
npm run lint # ESLint (via expo lint)
npx expo start --clear # Restart Metro with cache cleared (after app.json changes)
npx expo install <package> # Install a package matching the project's SDKAPI error: 404when sending a message — restart Metro withnpx expo start --clear. The API route is only served whenweb.output: "server"is in effect, and that change requires a full bundler restart.- Markdown / drawer / keyboard behavior looks broken — make sure you're running in the dev client (
npm run ios/npm run android), not Expo Go. These features need native code. - Clerk says "publishable key is invalid" — double-check
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEYin.env.localand restart Metro. OPENAI_API_KEY is not set— the key must be in.env.local(noEXPO_PUBLIC_prefix) and Metro must be restarted after adding it.
Built live on the notJust.dev YouTube channel. Subscribe for more full-stack tutorials.
MIT