by InlinexDev

Lessons from Building 8 Production Web Applications as a Solo Developer

Key lessons learned from building and maintaining 8 production web applications spanning Node.js, Python, Shopify, and e-commerce integrations.

lessons learnedweb developmentsolo developerbest practicesportfolio

The Portfolio

Over the past year, I built and deployed 8 production web applications: a WhatsApp booking system, an international shipping platform, a B2B wholesale portal, an AI image processing SaaS, a blog automation engine, a multi-channel inventory sync, a supplier stock dashboard, and a Shopify app. Here's what I learned.

Lesson 1: Start with the Database Schema

Every project that started with a solid database design went smoother than those where the schema evolved reactively. Spend the first day sketching your tables, relationships, and constraints.

-- This upfront planning saves weeks of refactoring later
CREATE TABLE bookings (
  id SERIAL PRIMARY KEY,
  customer_phone VARCHAR(20) NOT NULL,
  visit_date DATE NOT NULL,
  time_slot TIME NOT NULL,
  purpose VARCHAR(50) NOT NULL,
  is_blocking BOOLEAN DEFAULT true,
  status VARCHAR(20) DEFAULT 'confirmed',
  created_at TIMESTAMPTZ DEFAULT NOW(),
  
  CONSTRAINT valid_status CHECK (status IN ('confirmed', 'cancelled', 'completed')),
  CONSTRAINT unique_blocking_slot UNIQUE (visit_date, time_slot) 
    WHERE is_blocking = true AND status = 'confirmed'
);

The conditional unique constraint above prevents double-booking blocking slots. Getting this right at the schema level means your application code can be simpler.

Lesson 2: Railway Is Your Best Friend

I deployed all 8 applications on Railway. The total monthly cost across all projects: under $50. What would have been complex AWS infrastructure is a few clicks:

  • PostgreSQL databases provisioned instantly
  • Automatic HTTPS on custom domains
  • GitHub push-to-deploy
  • Environment variable management
  • Logs and monitoring built in

For solo developers, managed platforms like Railway aren't just convenient — they're essential. Time spent on infrastructure is time not spent on features.

Lesson 3: External APIs Will Break

Every project integrates with at least one external API. They all have issues:

  • WhatsApp Cloud API: Template approval takes days, rate limits are strict
  • FedEx API: Sandbox is unreliable, production errors are cryptic
  • Shopee API: Documentation has gaps, signature flow is complex
  • Google Places API: Field masks are required but poorly documented
  • Shopify API: Rate limits hit during bulk operations

The pattern: always wrap external APIs in a client class with retry logic, logging, and graceful degradation.

class ExternalApiClient {
  async request(endpoint, options, retries = 3) {
    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        const response = await fetch(endpoint, options);
        if (response.ok) return response.json();
        
        if (response.status === 429 && attempt < retries) {
          await sleep(attempt * 2000); // Exponential backoff
          continue;
        }
        
        throw new Error(`API error: ${response.status}`);
      } catch (err) {
        if (attempt === retries) throw err;
        await sleep(attempt * 1000);
      }
    }
  }
}

Lesson 4: Logging Saves Hours of Debugging

Structured logging from day one. Not console.log('something happened'), but:

console.log(JSON.stringify({
  level: 'info',
  event: 'booking_created',
  bookingId: booking.id,
  phone: booking.phone.slice(-4), // Last 4 digits only
  date: booking.date,
  duration: `${Date.now() - startTime}ms`
}));

When something breaks in production at 2 AM, structured logs tell you exactly what happened.

Lesson 5: Ship the Minimum, Iterate Fast

Pixel Prep launched with one feature: background removal. No batch processing, no marketplace presets, no credit system. Just upload an image, get it back without a background.

User feedback shaped everything that followed. Features I thought were essential (color backgrounds, shadow effects) weren't requested once. Features I didn't plan (batch ZIP upload, Shopee-specific sizing) were requested immediately.

Lesson 6: Automate What Hurts

Don't automate everything. Automate the things that cause actual pain:

  • Inventory sync — overselling caused refunds and bad reviews
  • Booking reminders — no-shows wasted staff time
  • Blog generation — SEO requires consistent publishing
  • Stock checking — 2 hours/day of manual work

Leave everything else manual until it becomes painful enough to justify automation.

Lesson 7: PostgreSQL Is Almost Always the Right Database

Across 8 projects with different requirements — booking slots, shipping documents, product catalogs, user accounts, sync states — PostgreSQL handled everything. Its JSONB columns cover the semi-structured cases, and its query capabilities eliminate the need for complex application-side data processing.

Lesson 8: Authentication Doesn't Need to Be Complex

For B2B portals and internal tools, simple JWT + httpOnly cookies work fine. OAuth2 is necessary for Shopify apps (because Shopify requires it) but overkill for a dealer portal with 30 users.

Lesson 9: Tests Save You When Refactoring

I won't pretend I had 90% coverage on every project. But the projects where I wrote tests for critical paths — payment processing, inventory calculations, booking conflict detection — were the ones where I could refactor confidently.

Minimum viable testing:

// Test the business logic, not the HTTP layer
describe('Booking Conflicts', () => {
  test('rejects overlapping blocking bookings', async () => {
    await createBooking({ date: '2026-04-15', time: '10:00', blocking: true });
    await expect(
      createBooking({ date: '2026-04-15', time: '10:00', blocking: true })
    ).rejects.toThrow('Slot already booked');
  });

  test('allows overlapping non-blocking bookings', async () => {
    await createBooking({ date: '2026-04-15', time: '10:00', blocking: false });
    const result = await createBooking({ date: '2026-04-15', time: '10:00', blocking: false });
    expect(result.id).toBeDefined();
  });
});

Lesson 10: Document Your Future Self

Six months from now, you won't remember why that FedEx API call has a specific timeout or why the Shopee token refresh runs every 3.5 hours. Comment the "why", not the "what":

// Shopee access tokens expire in 4 hours.
// Refresh at 3.5 hours to avoid race conditions
// during concurrent requests near expiry.
const TOKEN_REFRESH_INTERVAL = 3.5 * 60 * 60 * 1000;

The Bigger Picture

Building 8 production applications taught me that the technology matters less than the execution. Node.js or Python, React or vanilla JS, Prisma or raw SQL — these are implementation details. What matters is:

  1. Understanding the user's actual problem
  2. Shipping something that solves it quickly
  3. Making it reliable enough to run without constant attention
  4. Iterating based on real feedback

The best code is the code you don't have to think about because it just works.