← All Articles
Case Study6 min read

InkMatching Case Study: Building a Tattoo Discovery App From Scratch

Youness Haji

Youness Haji

October 20, 2024

InkMatching started as a conversation with a friend who wanted a specific tattoo style but couldn't find the right artist in Montreal. Yelp was useless. Instagram required hours of scrolling. Google Maps showed studios, not individual artists or styles.

That conversation became an app. Here's the full story — the technical decisions, the business lessons, and the mistakes I'd correct if I started over.

The Problem

Finding a tattoo artist is broken. Here's why:

  1. Discovery is fragmented — Artists are scattered across Instagram, Google, Yelp, and word of mouth
  2. Style matching is manual — You have to visually scan hundreds of portfolios to find your style
  3. Location filtering is crude — "Tattoo shops near me" shows studios, not artists who specialize in your style
  4. Booking is disconnected — Once you find an artist, you have to DM them, email them, or call the shop

InkMatching solves this by combining style-based discovery with location filtering and direct booking.

Technical Architecture

Stack Decision

I chose React Native with Expo for cross-platform mobile development. The rationale:

  • One codebase for iOS and Android — Critical for a solo developer on a budget
  • React knowledge transfer — I was already proficient in React/Next.js
  • Expo's managed workflow — Camera, location, push notifications without native code

Firebase for the backend:

  • Firestore — Real-time database for artist profiles, portfolios, and reviews
  • Firebase Auth — Google, Apple, and email authentication
  • Firebase Storage — Portfolio image hosting with automatic resizing
  • Cloud Functions — Booking notifications and admin operations

Data Model

The core data model has three entities:

typescript
interface Artist {
  id: string;
  name: string;
  studio: string;
  location: GeoPoint;
  styles: TattooStyle[];  // e.g., "Japanese", "Realism", "Minimalist"
  portfolio: PortfolioImage[];
  rating: number;
  reviewCount: number;
  availability: AvailabilitySlot[];
  hourlyRate: { min: number; max: number };
}

interface PortfolioImage {
  id: string;
  url: string;
  style: TattooStyle;
  bodyPart: string;
  tags: string[];
}

interface Booking {
  id: string;
  userId: string;
  artistId: string;
  date: string;
  status: 'pending' | 'confirmed' | 'completed' | 'cancelled';
  style: TattooStyle;
  description: string;
  referenceImages: string[];
}

The Discovery Algorithm

The core feature is style-based discovery. When a user selects "Japanese Traditional" and their location, the app needs to:

  1. Filter artists by style
  2. Sort by distance and rating
  3. Show portfolio images matching that specific style

Firestore doesn't support complex geospatial queries natively. I used GeoFirestore for location-based filtering combined with Firestore's array-contains for style filtering:

typescript
async function discoverArtists(
  style: TattooStyle,
  userLocation: GeoPoint,
  radiusKm: number
): Promise<Artist[]> {
  // GeoFirestore handles the geohash-based proximity query
  const nearbyArtists = await geoFirestore
    .collection('artists')
    .near({ center: userLocation, radius: radiusKm })
    .get();

  // Client-side filter by style (Firestore limitation)
  return nearbyArtists.docs
    .map(doc => doc.data() as Artist)
    .filter(artist => artist.styles.includes(style))
    .sort((a, b) => b.rating - a.rating);
}

This hybrid approach works well for a city-scale app. For a national-scale app, I'd move to Algolia or Elasticsearch for combined geo + attribute filtering.

Design Decisions

The Swipe Interface

I prototyped three discovery UIs:

  1. Grid view — Like Instagram explore. Shows many images but feels overwhelming.
  2. List view — Like Yelp. Familiar but boring for a visual product.
  3. Card swipe — Like Tinder. Focuses attention on one portfolio piece at a time.

User testing with 10 people showed the swipe interface had the highest engagement time and the most "ooh, I like that" reactions. Visual discovery is inherently a one-at-a-time experience.

Portfolio-First, Not Profile-First

Early versions showed artist profiles first (name, bio, rating) with portfolio images below the fold. Users consistently scrolled past the profile to see the work.

I flipped it: portfolio images first, artist info on tap. This matched how people actually discover tattoo artists — they see work they love, then want to know who made it.

Challenges and Solutions

Challenge 1: Image Loading Performance

A portfolio-heavy app with hundreds of high-resolution tattoo images needs aggressive optimization:

  • Progressive loading — Show low-res placeholder, load full resolution on demand
  • Firebase Storage resize extension — Automatically generates thumbnails on upload
  • React Native Fast Image — Caches images aggressively, prioritizes visible images
  • Lazy loading — Only load images within 2 viewport heights of the scroll position

Challenge 2: Artist Onboarding

Getting artists to join the platform was the hardest non-technical challenge. I solved it by:

  1. Manually creating profiles for 50 Montreal artists using their public Instagram portfolios
  2. Reaching out individually to let them claim and customize their profiles
  3. Offering free featured placement for the first 3 months

This "build it and they'll come" approach worked because artists could immediately see their work presented beautifully with zero effort on their part.

Challenge 3: Booking Without Building a Full Scheduling System

V1 didn't need a full booking system. I implemented a simple "Request Booking" flow:

  1. User fills out a form (style, size, placement, description, reference images)
  2. Request is sent to the artist's email
  3. Artist confirms or declines via email link
  4. User is notified of the decision

Simple? Yes. But it validated the concept without building a complex calendar system.

Results

After 3 months on the App Store:

  • 500+ downloads (organic, no paid acquisition)
  • 45 active artists on the platform
  • 120+ booking requests sent
  • 4.6 rating on the App Store (28 reviews)
  • $0 revenue (free app, monetization planned for V2)

What I'd Do Differently

1. Start With a Web App

A web app would have been faster to build, easier to iterate on, and more discoverable via SEO. Mobile could have come in V2 after validating the concept.

2. Build for One City First

I designed the app for "anywhere" but marketed it in Montreal. The data model and UI should have been Montreal-specific from the start, then generalized later.

3. Charge From Day 1

Free apps attract everyone, including people who aren't serious. A small fee ($2/month for artists, free for users) would have filtered for committed participants and validated willingness to pay.

Conclusion

InkMatching taught me more about product development than any side project ever could. The technical challenges were real but solvable. The hard parts were non-technical: getting artists to join, understanding user behavior, and making scope decisions under time pressure.

If you're thinking about building a marketplace app, start smaller than you think, validate with real users before scaling, and remember that the technology is the easy part.


Interested in the technical details or thinking about building a similar platform? Let's talk — I love discussing product development.