Tomilola.ng
Back to blog

July 10, 2026

How I Built Petizzy: Specs, Stack, Architecture, and a Free React Native Starter

From a June tweet about finding a breeding partner to App Store approval in July: the specs, stack, architecture decisions, and free Expo starter behind Petizzy.

PetizzyReact NativeExpoSpec-Driven DevelopmentMobile App
How I Built Petizzy: Specs, Stack, Architecture, and a Free React Native Starter

It started with a real problem. On June 13th of 2026, I posted on X looking for an American Eskimo owner around my area, someone open to cross breed my pet dog with theirs. Simple ask. Local. Specific.

After a while, I had a bigger thought, what if there was a social media for pet owners. Find people to cross-breed with. Get pet-related products on the platform. Free to use, sustained by me.

"Let me cook."

Then I actually started cooking.

The core MVP got done in eight days. Auth, pet profiles, ranked feed, DMs, push notifications, moderation, the real product loops. Bright colors. Fredoka and Nunito. Pets as first-class identities, not only captions under a human selfie.

(I wrote the day-by-day version of that sprint in I Built Petizzy in 8 Days. This piece is the deeper cut: specs, stack, architecture, and what I'd do differently.)

Then the second chapter started.

From around mid-June, roughly the 19th, through early July, there was no idle time. Privacy policy. Terms. Child safety. Abuse and reporting documentation. A ton of design changes. Store readiness. The unsexy work that turns an MVP into something Apple will actually approve. I bought the domain. I paid for the servers. This wasn't a client invoice, it was me putting money and nights into something I cared about.

I submitted Petizzy to the App Store on 3 July 2026. It was reviewed, approved, and live on 8 July — Petizzy on the App Store. Google Play had come first. This wasn't my first App Store launch either. On 4 February 2026 I shipped SteadyXchange, a gift card and crypto exchange app. So Petizzy wasn't me discovering review for the first time. It was me applying what already worked. Between SteadyXchange and this round, I learned the fastest path through Apple's checklist: privacy labels, account deletion, safety docs, screenshots that don't look like placeholders.

But here's the part most people miss when they see a polished app and assume it appeared fully formed:

Petizzy was never just about pets.

It's a passion project, the kind of thing I build when I'm free from client work, because I can't sit still and I love shipping. From the product brief onward, it was also meant to be a showcase: something a founder or agency could download, tap through, and immediately understand how I engineer. Not a tutorial clone. Not a half-finished demo. Proof of what I build when the motivation is real.

This article is about how that happened, the specs, the stack, the architecture decisions, and the reusable foundations underneath it. The stuff that doesn't fit in a launch screenshot.

If you stay until the end, I'll give you access to the exact React Native + Expo starter template I used to build it.

No more on that for now. Coffee first.

Why I Build Real Products

I didn't learn software by watching someone else build a todo app for the fifteenth time.

I learnt by shipping something that has to survive real users, real edge cases, and real store review. Tutorial projects teach syntax. Products teach judgment.

When you build a real product, you can't skip the boring parts. Session expiry. OTP cooldown. What happens when someone blocks a user mid-conversation. Whether a feed impression counts as a "view" or whether you only count intentional opens. Those decisions don't show up in YouTube thumbnails. They show up the moment someone actually uses your app.

Petizzy forced all of that. Co-owned pets. Soft deletes. Privacy settings. Report flows. Admin screens inside the same mobile app. Push notifications when the app is closed, Server sent events when it's open. None of that is glamorous. All of it is the difference between a portfolio screenshot and a product.

That's why I keep building real things. Every shipped product becomes leverage for the next one. Petizzy's eight-day MVP was possible because I wasn't inventing a backend from scratch on Day 1. I was extending a foundation I'd already refined. The weeks after that, the polish, the policies, the design passes, were possible because I was still obsessed with it.

I'm open to work. Petizzy is a sample of what I build when I'm between client fire and I still need to create. Passion is why this one got as far as two stores. Not a pitch deck. Not a committee. Just me, a problem I felt, and the refusal to leave it half-done.

Reminder

Still here? Good. There's a free React Native + Expo starter waiting at the end, the same foundation shape I used for Petizzy's mobile app. Keep reading, or you can skip/scroll till you see Free React Native Starter Template.

Spec-Driven Development

This is the biggest leverage point in how I work, and most people skip it.

Before I write serious code, I write human-readable specifications. Not corporate PRD theater. More like a detailed diary for future-me, other engineers, and AI coding agents.

For Petizzy, that meant documents like:

I treat ambiguity as a bug in the planning phase, not a surprise in production.

When I write a spec, I'm answering questions like:

That last one matters. Petizzy's docs are explicit about exclusions: payments, marketplace, vet bookings, crossing-request workflows, location matching, AI recommendations. Saying no early keeps the MVP small but complete.

What each kind of doc is for

People sometimes ask why I don't just keep one giant README. Because different readers need different truths.

The Product Brief is the north star. It says Petizzy is a playful pet-owner social app, that pets get digital identity, that breeding/crossing discovery is a local use case worth designing toward later, and that v1 is primarily a showcase product, not an instant business. If a feature doesn't serve that brief, it waits.

The Feature Spec is the build order and behavior contract. It tells you to confirm repo setup before screens, connect the API before inventing UI state, and ship auth before discover. It also encodes the boring-but-critical stuff: loading states, error messages, protected routes, auto-assigned usernames.

The API Contract is what stops mobile and backend from drifting into incompatible fantasies. When comments use a content field, or registration doesn't return login tokens, that belongs in a contract, not in a Slack message you'll forget in a few days.

Business Rules and Project Overview catch the product logic that doesn't fit neatly into a single endpoint: intentional views, ownership roles, block behavior, feed ranking philosophy, admin boundaries.

Task files turn all of that into executable slices. One task might be "notifications SSE and activity events." Another might be "direct messages." An agent can pick up a task without needing the entire product history loaded into its head.

Why this works so well with AI

Vague prompts produce vague software. That's not a hot take. It's just how these tools behave.

If I tell an agent "build a pet social app," I'll get generic CRUD with random assumptions. If I hand it a feature spec that says registration is verification-first, usernames are auto-assigned, posts can be caption-only or media-only, and intentional views are recorded only on detail/comments open, the agent has almost nowhere left to invent nonsense.

Specs don't replace engineering. They compress the decision space. Humans still make the hard calls. AI just stops wasting cycles on the wrong ones.

I also keep client handoff docs (gist/) separate from internal specs. Frontend/mobile consumers get HTTP methods, paths, auth, payloads, status codes, SSE event names — not migration steps or internal module gossip. That boundary keeps the API usable by people who will never open the backend repo.

There's also an AGENTS.md on the backend side that tells coding agents how to behave in the repo: keep route handlers thin, don't document modules that don't exist, don't reintroduce archived product concepts into the starter core. That file exists because AI without guardrails will "helpfully" invent architecture.

If you've ever watched an AI coding session go off the rails, the problem usually wasn't the model. It was the missing brief.

How I Use AI

Here is the tradeoff.

AI is not replacing engineering. It's multiplying the parts of engineering that used to be slow: navigation, boilerplate, first drafts of docs, exploring an unfamiliar module, generating a task breakdown from a spec, reviewing whether architecture still matches reality.

I use two tools heavily:

The combination matters. Spec-first development makes both tools dramatically more useful. Cursor stops guessing screen behavior because the feature spec already decided it. Codex stops inventing architecture because the project overview and stack docs already constrain it.

I still review everything. I still reject bad abstractions. I still decide what ships. AI just means I can move at the speed the Petizzy timeline required without pretending I typed every line in a cave by candlelight.

A practical example from this build: once the notifications contract was written — REST inbox, mark-as-read, device-token registration, SSE stream, push tap navigation — implementing the mobile side became assembly, not invention. The hard thinking had already happened on paper.

If someone tells you they "vibe coded" a production social app with no specs and no architecture, either the app is shallow or the story is.

The Tech Stack

What Petizzy actually runs on, and why.

Mobile: Expo, React Native, TypeScript

The mobile app is an Expo managed React Native app with TypeScript, Expo Router, and NativeWind.

I chose Expo because I wanted Android-first shipping without drowning in native project ceremony on Day 1. EAS handles preview and production builds. Local expo run:ios / expo run:android exist when I need native loops. For a showcase MVP that had to hit Google Play quickly, that tradeoff is still correct.

Expo Router gives file-based navigation with public and protected groups. Auth screens live under a public group. The real app — tabs, pets, posts, DMs, settings, admin — lives behind protected routing. That structure matches how I think about product surfaces: unauthenticated journey vs authenticated product.

NativeWind (Tailwind for React Native) keeps styling fast and consistent with shared tokens. Petizzy needed to feel branded, not like a default React Native starter. Fredoka for display energy, Nunito for readable UI text, warm yellow splash, playful but not childish. Design systems matter even when you're moving fast, especially when you're moving fast.

TanStack Query owns server state: feeds, pagination, mutations, cache invalidation, reference data. I don't want every screen inventing its own fetch-and-hope lifecycle. Optimistic follows, bookmark toggles, comment refreshes — Query makes those patterns boring in the best way.

Secure session storage uses expo-secure-store. Tokens don't belong in AsyncStorage wishful thinking.

Notifications are dual-path: REST + SSE in the foreground via react-native-sse, Expo push when the app is backgrounded or closed. That split is deliberate. SSE is great for live inbox updates while you're in the app. Push is what makes the product feel alive when you're not.

The five-tab shell — home, discover, create, messages, profile — is the product's spine. Everything else hangs off that navigation model: post detail, pet profiles, public accounts, settings, bookmarks, admin. If the tab model is wrong, the whole app feels wrong. We got that right early and kept shipping into it.

Alternatives I considered and still wouldn't switch to for this product shape:

I'd choose this mobile stack again tomorrow for a consumer MVP.

Backend: FastAPI, Tortoise, PostgreSQL-ready

The API is Python 3.13 + FastAPI, managed with uv, ORM'd with Tortoise, migrated with Aerich.

Petizzy didn't start as an empty python folder. It started as a reusable FastAPI base I'd already refined — auth, profiles, files, and notifications battle-tested — then grew product modules on top: taxonomy, pets, posts, discover, messages, moderation, telemetry.

Why FastAPI? Async-native, excellent OpenAPI ergonomics, Pydantic validation that keeps contracts honest, and a service-layer style that stays readable when domains multiply. Thin route handlers. Business logic in services. Schemas as the contract. That pattern scales across products without turning into framework religion.

Why Tortoise + Aerich? Async ORM that fits FastAPI cleanly. Local boot can use SQLite so a new machine is productive in minutes. Production shape targets PostgreSQL. That dual-path is intentional: fast first boot, serious database when it matters.

Auth is email/password with Argon2 hashing, JWT access/refresh tokens, OTP verification, and token-version revocation on logout-style flows. Registration is verification-first — you don't get a signed-in session just because you submitted a form. That decision shows up everywhere in the mobile auth UX, and it came from the spec, not from improvisation mid-build.

Files go through Cloudflare R2 via boto3 when uploads are enabled. Owner-scoped assets for avatars, pet images, post media. Optional until you need it, required once users start posting photos of their dogs at 11pm.

Email through Resend. Push through the Expo server SDK. Rate limiting with SlowAPI on sensitive auth and contact routes. There's also scaffolding for Paystack and Prembly in the integration layer — present because the template serves many products, unused by Petizzy MVP on purpose.

SSE for notifications instead of jumping straight to a Redis-backed WebSocket farm. For this stage, an authenticated in-process stream with heartbeat and replay cursor was the right complexity budget. I can always graduate the transport later. Premature distributed messaging is how showcase MVPs die in infrastructure quicksand.

Marketing web: Astro on Netlify

Petizzy.com isn't a three-section landing page with a fake testimonial.

It's an Astro + React + Tailwind site deployed on Netlify, with real feed viewing, auth flows, post detail pages, SEO, and app download prompts. The marketing site had to feel like the product's public face — same brand energy, same API, useful enough that someone who finds a shared post on the web understands what Petizzy is.

Astro was the right call for a content/marketing surface that still needs interactive React islands. Netlify keeps deploy simple. Shadcn-style UI pieces where interaction needs them. The site also carries compliance pages and contact support, the unglamorous stuff that makes a public product feel finished.

Architecture

I don't build apps as one-off snowflakes.

I build foundations.

The backend template underneath Petizzy is intentionally generic at the core: accounts, base files, contact, notifications/records, config, optional integrations. Product domains plug in with a consistent shape:

app/<domain>/
  models.py
  schemas.py
  services.py
  routes.py
  permissions.py  # when needed

Pets, posts, taxonomy, discover, messages, moderation, telemetry — same grammar. That consistency is what lets me (or an agent) add a module without reinventing project structure every time.

On mobile, the split is similar:

Reusable architecture matters because ServiceBlocks products often share the same underlying ideas: auth that doesn't embarrass you, file uploads that don't leak, notifications that actually arrive, admin capabilities when trust and safety show up, settings/privacy when stores and users demand them.

Petizzy is the consumer-facing proof. The architecture is the business asset.

A few product decisions that show architectural taste beyond feature count:

The point of all this structure isn't bureaucracy. It's speed with a conscience. When the next feature arrives — and it always does — you know where it lives.

Why I'm Not Sharing My Backend Template

People ask for the backend template a lot.

I'm not releasing it.

Not because I enjoy being mysterious. Because that template represents years of refinement across real products. Auth flows, OTP hardening, file lifecycle, notification patterns, integration scaffolding, the boring operational details that only show up after you've shipped more than once. It powers many ServiceBlocks products. It is one of the clearest competitive advantages I have as an engineer-for-hire and as a studio.

A good backend template isn't "FastAPI hello world plus JWT copy-paste." It's opinionated answers to ugly questions:

Those answers took real products to earn. Sharing the mobile starter helps the community and still leaves room for the work I get hired to do. Sharing the full backend template would be me giving away the factory.

I'm happy to talk architecture. I'm happy to teach the patterns. I'm happy to explain why verification-first registration beats "return tokens immediately and pray." I just won't zip up the template and hand over the leverage.

The mobile starter is different. We'll get there.

What I'd Do Differently

I'm not going to pretend the build was perfect. That would be a lie, and you'd smell it.

Some docs lagged the product. Specs are powerful, but only if you keep them honest. A few task files and older notes drifted while features shipped. The rule I enforce harder now: docs must not overstate repo reality. If it's not implemented, don't write it like it is.

Feed ranking vs "latest-first" language got nuanced. The product evolved toward trending/precomputed ranking with unviewed prioritization. Early copy sometimes described a simpler feed. Reality won — as it should — but I should have updated the mobile overview language faster.

SSE is in-process. Fine for this stage. If Petizzy ever needed multiple API workers with shared live fanout, I'd introduce a proper broker instead of pretending heartbeats scale forever. I chose the simpler system knowingly. I'd still choose it for an MVP. I wouldn't choose it forever without revisiting.

iOS came after Android momentum on Petizzy. Android-first was correct for the original launch goal. I submitted on 3 July and went live on 8 July — and by then I'd learned the fastest approval path from SteadyXchange plus this round: have privacy, terms, child safety, and abuse docs ready; don't give reviewers an easy rejection. If a client's primary market is iOS, I'd still sequence that store work earlier in that product's timeline, not as a scramble after Android feels "done."

Marketplace temptation. The original idea space includes crossing/breeding discovery, products, services. The MVP deliberately refused to become a business platform overnight. That restraint was right. The hard part is keeping that discipline when feature ideas are exciting.

The eight-day MVP was not the finished public face. Core loops shipped fast. What you see on the stores today is mid-June through early July of constant improvement, design passes, compliance pages, safety documentation, the stuff that doesn't show up in a feature list but decides whether Apple says yes. For client work, I'd still ship an MVP fast; I'd also plan the post-MVP hardening window on purpose instead of pretending day eight is the end.

Template naming scars. The backend package still carries base-template DNA in places. Harmless technically. A reminder that Petizzy grew out of a reusable system rather than a greenfield pet startup fantasy.

A couple of screens are still intentionally thin. Pet subscribers list, for example — subscribe/unsubscribe works on the pet profile, but the dedicated list screen waited. That's fine for an MVP. It's also a reminder that "done" and "complete catalog of every possible screen" are not the same thing.

None of these are regrets big enough to undo the stack. They're the kind of notes you only get from shipping.

Free React Native Starter Template

Alright. Here's the part I promised.

I've decided to release the exact React Native + Expo starter template used to build Petizzy, free.

Not a watered-down toy. The real mobile foundation: Expo, TypeScript, Expo Router, NativeWind, auth/session patterns, typed API client shape, provider structure, the kind of project layout that lets you build a serious consumer app without spending your first week inventing folders.

When I started Petizzy's UI, I wasn't debating whether to use Expo Router or invent a navigation religion. I wasn't arguing about where API clients live. Those decisions were already settled in the starter. That's how you get from brand night to feed screens without losing a week to project archaeology.

Important: this is the mobile template only.

The backend template is not included. I already explained why. It powers many ServiceBlocks products and represents years of work I'm not giving away as a free zip file. If you want that level of backend architecture on a project, that's what hiring me is for.

If the mobile starter helps you:

I'll link the template from tomilola.ng and my GitHub. If you're reading this on the blog, the download/CTA will be right here with the published post.

Use it. Break it. Ship something with it. Then go build a product people can actually open.

Closing

Petizzy started with that Akoka tweet, a real ask about finding another pet owner nearby.

The MVP landed in eight days because the foundations were already real: a hardened FastAPI template, a serious mobile starter, and a spec-driven workflow that keeps humans and AI aligned. What went live on the App Store on 8 July is what happens when you don't stop after the MVP, when you keep improving because you actually care.

The pets were the excuse. The craft was the point. The passion is why it got this far.

If you take one practical thing from this article, take this:

Write the product down before you code it. Choose boring leverage over fashionable complexity. Ship something real enough that strangers can judge you.

I'm Tomi. I'm open to work. I build mobile apps, backend APIs, AI-assisted product workflows, production MVPs, and internal business software for startups, founders, businesses, and agencies. Petizzy is what I build when I'm free and restless, a sample of the standard, not a one-off miracle.

If you're building something interesting, I'd love to hear about it.