A real-world personal diary app built with Expo (React Native) and Kroxt BaaS — demonstrating how to wire authentication, database collections, and real-time subscriptions into a production-quality mobile app.
Dear Diary is a dogfooding reference app created by the Kroxt team to showcase how developers can integrate Kroxt BaaS into a React Native project from scratch.
It covers the full lifecycle of a Kroxt-powered app:
| Feature | Kroxt API used |
|---|---|
| User registration | baas.auth.register() |
| Send OTP email after signup | Kroxt sends automatically on register |
| Verify OTP code | baas.communication.verifyOtp() |
| Email/password login | baas.auth.login() |
| Session persistence | baas.auth.getCachedUser() |
| Sign out | baas.auth.logout() |
| Create a diary entry | baas.collection("lists").create() |
| Read all diary entries | baas.collection("lists").find() |
| Read a single entry | baas.collection("lists").get(id) |
| Edit a diary entry | baas.collection("lists").update(id, data) |
| Delete a diary entry | baas.collection("lists").delete(id) |
| Live updates without polling | baas.realtime.collection("lists").subscribe() |
| Layer | Technology |
|---|---|
| Framework | Expo v56 (React Native) |
| Routing | Expo Router v4 (file-based) |
| Backend | Kroxt BaaS via npm install @kroxt/baas-sdk |
| Session Storage | @react-native-async-storage/async-storage |
| Language | TypeScript |
src/
├── app/ # Expo Router screens (file-based routing)
│ ├── _layout.tsx # Root Stack navigator + animated splash screen
│ ├── index.tsx # Login screen
│ ├── register.tsx # Registration screen (name, email, password)
│ ├── otp.tsx # OTP verification screen
│ ├── list.tsx # Entry list (with realtime subscription)
│ ├── new.tsx # Create new diary entry
│ └── detail.tsx # View / edit / delete a diary entry
│
├── components/
│ ├── AppSplash.tsx # Animated in-app splash screen
│ ├── MoodChip.tsx # Mood selector pill (happy / neutral / sad)
│ ├── PrimaryButton.tsx # Button with loading state + glass variant
│ ├── RubberStampBadge.tsx # Decorative icon stamp
│ └── UnderlineInput.tsx # Text input with eye-toggle for passwords
│
├── constants/
│ └── theme.ts # Design system — colors, fonts, spacing
│
├── utils/
│ └── mockData.ts # Mock data seams (for UI-only development)
│
├── config.ts # Reads env vars, throws if missing
└── kroxt.ts # Kroxt BaaS client singleton
git clone https://github.com/kroxt/dear-diary-rn.git
cd dear-diary-rn
npm install- Sign up at kroxt-baas.vercel.app
- Create a new project
- Copy your Project ID and API Key from the dashboard
Create a .env file at the root of the project:
EXPO_PUBLIC_KROXT_PROJECT_ID=your_project_id_here
EXPO_PUBLIC_KROXT_API_KEY=your_api_key_hereWhy
EXPO_PUBLIC_prefix? Expo only exposes env vars to the JS bundle if they are prefixed withEXPO_PUBLIC_. This is handled automatically — you just need to set them.
In your Kroxt dashboard, create a collection named lists with the following fields:
| Field | Type | Notes |
|---|---|---|
title |
String | Optional — entry heading |
body |
String | Required — main diary text |
mood |
String | "happy" or "neutral" or "sad" |
date |
String | Formatted date string, e.g. "August 9, 2026" |
npx expo startScan the QR code with Expo Go on your phone, or press a for Android emulator / i for iOS simulator.
import { Kroxt } from "@kroxt/baas-sdk";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { KROXT_API_KEY, KROXT_PROJECT_ID } from "./config";
export const baas = new Kroxt({
projectId: KROXT_PROJECT_ID,
apiKey: KROXT_API_KEY,
storage: AsyncStorage, // Persists the auth session on device
debug: true,
});
export default baas;The baas client is a singleton — import it into any screen that needs backend access.
Register (src/app/register.tsx):
await baas.auth.register({ name, email, password });
router.push({ pathname: '/otp', params: { email } });Verify OTP (src/app/otp.tsx):
await baas.communication.verifyOtp({ email, purpose: 'register', code });
router.replace('/list');Login (src/app/index.tsx):
const session = await baas.auth.login({ email, password });
if (session.user) router.push('/list');Get current user (src/app/list.tsx):
const user = await baas.auth.getCachedUser();
setUsername(user.displayName || user.name);Logout (src/app/list.tsx):
await baas.auth.logout();
router.replace('/');After registration, Kroxt automatically sends a 6-digit OTP email to the user. The app then calls baas.communication.verifyOtp() to validate the code and activate the account.
Verify the OTP code (src/app/otp.tsx):
const res = await baas.communication.verifyOtp({
email: email, // Passed from the register screen via router params
purpose: "signup", // Must match the purpose set during registration
code: fullCode, // 6-digit string joined from individual input fields
});
if (res.success) {
router.push('/list');
} else {
setError(res.message || 'Invalid verification code.');
}UX details implemented on the OTP screen:
- 6 individual digit input boxes with auto-advance focus on each keystroke
- Backspace steps focus back to the previous field automatically
- A live countdown timer (
0:47 → 0:00) before the Resend button becomes active - The email address is displayed dynamically in the subtitle (passed as a route param from the register screen)
- Full guard: if
emailis missing (e.g. direct deep link), an error is shown before any API call is made
Resend flow (timer-gated):
const handleResend = () => {
if (timer > 0) return; // Button is disabled while countdown is active
setTimer(59); // Reset countdown
// Call baas.communication to trigger a new OTP send here
};Create (src/app/new.tsx):
await baas.collection("lists").create({
title: "My first entry",
body: "Dear Diary, today was incredible...",
mood: "happy",
date: "August 9, 2026",
});Read all (src/app/list.tsx):
const docs = await baas.collection("lists").find();
const entries = docs.map(doc => ({
id: doc._id,
title: doc.data.title,
body: doc.data.body,
mood: doc.data.mood,
date: doc.data.date,
}));Important: Kroxt returns
Document<T>[]from.find()and.get(). User data lives underdoc.data, and the document ID isdoc._id.
Read one (src/app/detail.tsx):
const doc = await baas.collection("lists").get(id);Update (src/app/detail.tsx):
await baas.collection("lists").update(entry.id, {
title: editTitle,
body: editBody,
mood: editMood,
});Delete (src/app/detail.tsx):
await baas.collection("lists").delete(entry.id);The entry list subscribes to live database changes so any create, update, or delete reflects instantly — no polling or manual refresh needed (src/app/list.tsx):
useEffect(() => {
const channel = baas.realtime.collection("lists").subscribe();
channel.on("created", (doc) => {
setEntries(prev => [mapDoc(doc), ...prev]);
});
channel.on("updated", (doc) => {
setEntries(prev => prev.map(e => e.id === doc._id ? mapDoc(doc) : e));
});
channel.on("deleted", ({ documentId }) => {
setEntries(prev => prev.filter(e => e.id !== documentId));
});
return () => channel.unsubscribe(); // Clean up on unmount
}, []);The list screen renders animated placeholder cards while the initial fetch is in progress, using React Native's built-in Animated API — no extra packages needed.
Entry fields (title, body, mood, date) are passed as route params when tapping a card, so the detail screen renders immediately without a network fetch. A baas.collection.get(id) call is used only as a fallback for deep links.
Every button that triggers a network call shows a loading indicator:
- Login, Register — spinner inside
PrimaryButton - Save (new and edit) — header button text changes to
Saving... - Delete —
Deletetext swaps for a red spinner - Logout — icon and text swaps for a red spinner
Since Expo Go cannot display custom native splash screens, an animated in-app splash (AppSplash.tsx) renders over the entire navigator on first launch — feather icon springs in, title slides up, fades out — then unmounts itself cleanly.
To get a real custom splash screen and app icon (instead of the in-app workaround):
# Install EAS CLI
npm install -g eas-cli
# Configure your project
eas build:configure
# Build for Android (APK for testing)
eas build --platform android --profile preview
# Build for iOS (requires Apple Developer account)
eas build --platform ios --profile preview| Variable | Required | Description |
|---|---|---|
EXPO_PUBLIC_KROXT_PROJECT_ID |
Yes | Your Kroxt project ID |
EXPO_PUBLIC_KROXT_API_KEY |
Yes | Your Kroxt API key |
EXPO_PUBLIC_NODE_ENV |
Optional | development or production |
MIT — free to use as a reference implementation or starting point for your own Kroxt-powered React Native app.
Built with love by the Kroxt team · https://kroxt-baas.vercel.app