Takazudo Modular Docs

Type to search...

to open search from anywhere

Blob Storage

Netlify Blobs Storage

This document describes the Netlify Blobs storage implementation for notify subscriptions and reservations.

Overview

We use Netlify Blobs as a simple key-value store for persisting subscription and reservation data. It’s a serverless storage solution that integrates seamlessly with Netlify Functions.

Storage Architecture

Store Names

Data is stored in a single unified store with context-based separation:

ContextStore NamePurpose
Production (main branch)preorder-dataReal customer data
Preview/Developmentpreorder-data-previewTest data (isolated from production)

The store name is determined by the CONTEXT environment variable provided by Netlify:

const STORE_NAME =
  process.env.CONTEXT === 'production' ? 'preorder-data' : 'preorder-data-preview';

This separation ensures that testing on preview deploys never affects production data.

Key Patterns

All data is stored in the unified store with key prefixes:

notify:{productSlug}:{id}           # Individual notify subscription
index:notify:{productSlug}          # Index of subscription IDs per product
index:products:notify               # List of products with subscriptions

reservation:{productSlug}:{id}      # Individual reservation
index:reservation:{productSlug}     # Index of reservation IDs per product
index:products:reservation          # List of products with reservations

Data Types

NotifySubscription

interface NotifySubscription {
  id: string;              // UUID
  email: string;           // Normalized to lowercase
  productSlug: string;     // Product identifier
  createdAt: string;       // ISO timestamp
  notifiedAt?: string;     // ISO timestamp (when notified)
  status: 'pending' | 'notified' | 'unsubscribed';
}

Reservation

interface Reservation {
  id: string;              // Format: res_{timestamp}-{random}
  name: string;            // Customer name
  email: string;           // Normalized to lowercase
  productSlug: string;     // Product identifier
  createdAt: string;       // ISO timestamp
  status: 'pending' | 'confirmed' | 'cancelled' | 'fulfilled';
  notes?: string;          // Admin notes
}

Available Operations

Notify Subscriptions

FunctionDescription
saveNotifySubscription(email, productSlug)Create new subscription
getNotifySubscription(productSlug, id)Get subscription by ID
listNotifySubscriptions(productSlug)List all subscriptions for a product
updateNotifyStatus(productSlug, id, status)Update subscription status
markAsNotified(productSlug, ids)Bulk mark as notified
deleteNotifySubscription(productSlug, id)Delete a subscription
isEmailSubscribed(productSlug, email)Check for duplicate (case-insensitive)

Reservations

FunctionDescription
saveReservation(id, name, email, productSlug)Create new reservation
getReservation(productSlug, id)Get reservation by ID
listReservations(productSlug)List all reservations for a product
updateReservationStatus(productSlug, id, status, notes?)Update reservation status
deleteReservation(productSlug, id)Delete a reservation

Product Indexes

FunctionDescription
listNotifyProducts()List products with notify subscriptions
listReservationProducts()List products with reservations
listAllProducts()List all products (combined, sorted)

Development Commands

CommandStorage LocationUse Case
pnpm dev:fullLocal (.netlify/blobs-serve/)Next.js + local functions (offline blobs)
pnpm functions:serveLocal (.netlify/blobs-serve/)Standalone functions server (port 9999)

Local development with pnpm dev:full or pnpm functions:serve always uses offline/sandbox blob storage. It is not possible to access remote Netlify Blobs from local development due to Netlify’s security design. To test with real remote storage, deploy to a preview branch.

Testing with Offline Local Server

Step 1: Start Dev Server with Local API

pnpm dev:full

This starts the Netlify Functions server on port 9999 with offline blob storage plus the Next.js dev server on port 34434 that proxies /api/* requests to the functions server. Data is stored locally in .netlify/blobs-serve/ directory, completely isolated from production.

Step 2: Open Test Page

Navigate to: http://zmod.localhost:34434/test-notify-dialogs

This test page provides:

  • NotifyMe Dialog - Test restock notification signup
  • Reservation Dialog - Test product reservation

Step 3: Test the Dialogs

  1. Click “Open NotifyMe Dialog” or “Open Reservation Dialog”
  2. Fill in the form fields
  3. Submit and verify success/error responses

Step 4: Verify Stored Data

Check the local blob storage directory:

# List all stored blobs
ls -la .netlify/blobs-serve/

# View specific store contents (local dev uses preorder-data-preview)
cat .netlify/blobs-serve/preorder-data-preview/*

Example stored data structure:

.netlify/blobs-serve/
└── preorder-data-preview/
    ├── notify:test-product:uuid-1234
    ├── index:notify:test-product
    ├── index:products:notify
    ├── reservation:test-product:res_xxx
    ├── index:reservation:test-product
    └── index:products:reservation

Step 5: Clean Up Test Data

# Remove all local blob data
rm -rf .netlify/blobs-serve/*

Testing on Preview Deployments

To test with real remote blob storage, deploy to a preview branch:

  1. Push changes to a preview branch (preview or expreview/{topic-name})
  2. Wait for Netlify to deploy
  3. Access the preview URL (e.g., https://*--takazudomodular.netlify.app)
  4. Test the dialogs - data will be stored in preorder-data-preview store

Verify Remote Data via CLI

# List all keys in the preview store
netlify blob:list preorder-data-preview

# Get a specific key
netlify blob:get preorder-data-preview "notify:product-slug:uuid"

Remote blob data on preview deployments persists across deploys. Clean up test data manually if needed using netlify blob:delete.

Test Scenarios

ScenarioExpected Result
Submit valid emailSuccess message, data stored
Submit same email twice”既に登録されています” error
Submit reservationSuccess with reservation ID
Empty form submissionValidation prevents submit

Automated Testing

Unit Tests (Mocked)

pnpm test:unit

Unit tests use Jest mocks for @netlify/blobs, testing business logic without real storage.

E2E Tests (Offline Blobs)

pnpm test:e2e:netlify

E2E tests use Playwright with the offline Netlify dev server, testing the full user flow from UI to storage.

Implementation Files

FilePurpose
netlify/functions/shared/blob-store.tsCore CRUD operations
netlify/functions/shared/types.tsTypeScript interfaces
netlify/functions/notify-signup.tsNotify signup endpoint
netlify/functions/reservation.tsReservation endpoint
tests/unit/blob-store.test.tsUnit tests
tests/e2e/notify-dialogs.spec.tsE2E tests

Index Management

Indexes are automatically maintained when creating/deleting records:

  1. Product Index: Lists all IDs for a product
  2. Global Index: Lists all products with data

This enables efficient listing without scanning all keys.

Example: Listing Subscriptions

// 1. Get index for product
const index = await store.get('index:notify:product-abc', { type: 'json' });
// { ids: ['uuid-1', 'uuid-2', 'uuid-3'], updatedAt: '...' }

// 2. Fetch each subscription
const subscriptions = await Promise.all(
  index.ids.map(id => store.get(`notify:product-abc:${id}`, { type: 'json' }))
);

Email Handling

Normalization

Both notify subscriptions and reservations normalize emails to lowercase before storage. This ensures case-insensitive handling (e.g., User@Example.comuser@example.com).

Duplicate Prevention (Notify Only)

Email duplicates are prevented for notify subscriptions:

  • isEmailSubscribed() checks for existing pending subscriptions
  • Only pending status counts as “subscribed” (allows re-subscribe after notification)
  • Returns error if email already subscribed to a product

Reservations Allow Duplicates

Multiple reservations from the same email are allowed by design:

  • Customers may want to reserve multiple units
  • Each reservation gets a unique ID for tracking
  • No duplicate checking is performed

Admin Access Architecture

The Challenge

Local development (via netlify dev) cannot access remote Netlify Blobs. This is a security limitation by design. This creates challenges for building admin tools that need to view and manage production/preview data.

Solution: Admin API Endpoints

To enable admin access from external tools (like the zpreorder sub-app), we expose blob data through authenticated API endpoints:

┌─────────────────────────────────────────────────────────┐
│                  Netlify (Deployed)                     │
│  ┌──────────────────┐    ┌──────────────────────────┐  │
│  │   Main Site      │    │   Netlify Blobs          │  │
│  │   /api/admin/*   │◄──►│   preorder-data          │  │
│  │   (with auth)    │    │   preorder-data-preview  │  │
│  └────────▲─────────┘    └──────────────────────────┘  │
└───────────┼─────────────────────────────────────────────┘
            │ HTTPS + PREORDER_API_TOKEN

┌───────────┼─────────────────────────────────────────────┐
│  Local    │                                             │
│  ┌────────▼─────────┐                                   │
│  │  zpreorder│   Fetches from deployed preview   │
│  │  Sub-App         │   using auth token                │
│  └──────────────────┘                                   │
└─────────────────────────────────────────────────────────┘

Admin API Endpoints (Planned)

EndpointMethodPurpose
/api/admin/notify/listPOSTList all notify subscriptions
/api/admin/reservations/listPOSTList all reservations
/api/admin/notify/[id]/statusPOSTUpdate subscription status
/api/admin/reservations/[id]/statusPOSTUpdate reservation status
/api/admin/statsPOSTGet summary statistics

Authentication

Admin endpoints require the PREORDER_API_TOKEN header (a dedicated token for the preorder API, not Netlify’s PAT):

const response = await fetch('https://preview--takazudomodular.netlify.app/api/admin/notify/list', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${process.env.PREORDER_API_TOKEN}`,
  },
});

Development Modes for Admin Tools

ModeData SourceUse Case
MockLocal JSON filesUI development, fast iteration
RemoteDeployed preview APITesting with real data

The zpreorder sub-app supports both modes via environment variable:

# Remote mode (in sub-packages/zpreorder/.env)
VITE_BLOB_API_URL=https://preview--takazudomodular.netlify.app/api/admin
VITE_PREORDER_API_TOKEN=your-token-here

Notes

  • Netlify Blobs is a key-value store, not a relational database
  • No query capabilities - must fetch by exact key or use indexes
  • Suitable for small to medium datasets (< 10,000 items per “table”)
  • Data persists across deploys and function invocations
  • Store separation: Production uses preorder-data, preview/dev uses preorder-data-preview
  • Local development: Always uses offline sandbox mode (cannot access remote blobs)
  • Index race conditions: Index updates are not atomic; acceptable for low-traffic usage