npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@flavoai/fastfold

v1.4.0

Published

Zero-boilerplate backend for React apps with auto-generated CRUD and declarative security

Readme

Fastfold

Zero-boilerplate backend for React apps with Drizzle ORM integration, auto-generated CRUD, and declarative security. React Query powered for smart caching and background sync. Perfect for rapid UI development with minimal code.

Features

  • 🚀 Zero boilerplate - Auto-generated CRUD operations from Drizzle schemas
  • 🎯 Simple API - Just useQuery and useMutation (powered by React Query!)
  • 📦 Automatic caching - Smart cache invalidation and background refetching
  • 🗄️ Drizzle ORM - Full database power with relationships, migrations, and validation
  • 🔒 Declarative security - Role-based permissions per table
  • 🔧 TypeScript - Full type safety out of the box
  • 🤖 LLM-friendly - Small, predictable API surface
  • Production ready - PostgreSQL, MySQL, SQLite support with foreign keys
  • 🛠️ React Query DevTools - Optional debugging and cache inspection

Quick Start

1. Install Fastfold

npm install @flavoai/fastfold @tanstack/react-query

Note: React Query is a peer dependency for the client hooks.

2. Define Your Schema with Drizzle (30 seconds)

// schema.ts - Use Drizzle's powerful schema definition
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
import { relations } from 'drizzle-orm';

export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  email: text('email').notNull().unique(),
  name: text('name').notNull(),
  role: text('role').default('user'),
});

export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  title: text('title').notNull(),
  content: text('content'),
  published: integer('published', { mode: 'boolean' }).default(false),
  authorId: integer('author_id').references(() => users.id, { onDelete: 'cascade' }),
});

// Define relationships
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));

3. Create Your Backend (30 seconds)

// server.ts - Fastfold adds CRUD + Security to your Drizzle schema
import Fastfold, { Security } from '@flavoai/fastfold';
import path from 'path';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import Database from 'better-sqlite3';
import * as schema from './schema';

const db = drizzle(new Database('./app.db'), { schema });

await Fastfold.quickStart({
  // 🔗 DRIZZLE INTEGRATION - Use your existing schema
  drizzle: { db, schema },
  
  // 🔒 ADD SECURITY - Just specify security per table
  tables: {
    users: {
      security: Security.authenticated(),
      operations: ['read', 'update'] // Granular CRUD control
    },
    posts: {
      security: Security.owner('authorId'),
      operations: ['create', 'read', 'update', 'delete']
    }
  },

  // 🧩 (Optional) Serve your frontend SPA from the same server
  // Supports single or multiple static mounts
  staticFrontend: [
    {
      directory: path.resolve(process.cwd(), 'dist'), // your Vite build output
      urlPath: '/',            // mount at root
      spaFallback: true,       // route non-API requests to index.html
      excludePaths: ['/api', '/docs', /^\/assets\//],
      indexFile: 'index.html',
      staticOptions: { index: false, maxAge: '1h', immutable: true }
    },
    // Example: admin panel mounted at /admin
    // {
    //   directory: path.resolve(process.cwd(), 'admin-dist'),
    //   urlPath: '/admin',
    //   spaFallback: true,
    //   excludePaths: ['/api', '/docs']
    // }
  ]
});

// That's it! Full CRUD API with relationships and security

4. Build and Serve your Frontend (optional)

If you want to serve your SPA from Fastfold, build it with Vite (or similar) into dist/ (or your configured directory):

# From your frontend project
npm run build
# Ensure build outputs to ./dist relative to your server or set staticFrontend.directory accordingly

Then start Fastfold; your app will be served along with the API.

5. Minimal frontend you can copy-paste (CSP-friendly)

Create dist/index.html with:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Fastfold Quickstart</title>
    <link rel="stylesheet" href="app.css" />
  </head>
  <body>
    <h1>Fastfold Quickstart</h1>
    <p>Backend API is mounted at <code>/api</code>. Click to seed sample data, then list posts.</p>
    <div>
      <button id="seed">Seed Sample Data</button>
      <button id="load">Load Posts</button>
    </div>
    <div id="out" style="margin-top: 1rem"></div>
    <script defer src="app.js"></script>
  </body>
  </html>

Create dist/app.js with:

const out = document.getElementById('out');
const show = (html) => { out.innerHTML = html; };
document.getElementById('seed').onclick = async () => {
  const res = await fetch('/api/seed', { method: 'POST', headers: { 'content-type': 'application/json' } });
  const data = await res.json();
  show(`<div class="card">Seeded: <pre>${JSON.stringify(data, null, 2)}</pre></div>`);
};
document.getElementById('load').onclick = async () => {
  const res = await fetch('/api/posts?params=' + encodeURIComponent(JSON.stringify({ with: { author: true }, orderBy: { createdAt: 'desc' } })));
  const json = await res.json();
  const posts = json.data || [];
  show(posts.length ? posts.map(p => `<div class="card"><h3>${p.title}</h3><div>By: ${p.author?.name || 'Unknown'}</div><p>${p.content}</p></div>`).join('') : '<div class="card">No posts yet</div>');
};

Create dist/app.css with:

body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, 'Helvetica Neue', Arial, 'Noto Sans', 'Apple Color Emoji', 'Segoe UI Emoji'; margin: 2rem; }
h1 { margin: 0 0 1rem; }
button { padding: .5rem .75rem; }
.card { border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin: .5rem 0; }
code { background: #f6f8fa; padding: .2rem .4rem; border-radius: 4px; }

Now start the server and visit http://localhost:3001.

6. Use in React (30 seconds)

// App.tsx - Wrap your app with FastfoldProvider
import { FastfoldProvider, configureFastfold } from '@flavoai/fastfold/client';

// Configure your API endpoint
configureFastfold({ baseUrl: 'http://localhost:3001/api' });

function App() {
  return (
    <FastfoldProvider>
      <BlogList />
    </FastfoldProvider>
  );
}

// BlogList.tsx - Use Fastfold hooks (powered by React Query!)
import { useQuery, useCreate } from '@flavoai/fastfold/client';

function BlogList() {
  // 🎯 SIMPLE QUERY - Fetch data with relationships (with React Query caching!)
  const { data: posts, isLoading, error } = useQuery('posts', {
    where: { published: true },
    orderBy: { createdAt: 'desc' },
    with: { author: true } // Include relationships!
  });

  // 🚀 SIMPLE MUTATION - Create data with automatic cache invalidation
  const createPost = useCreate('posts');

  const handleCreate = () => {
    createPost.mutate({ 
      title: 'Hello World', 
      content: 'My first post!',
      published: true
    });
    // Cache automatically updates! No manual refetch needed 🎉
  };

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <button 
        onClick={handleCreate}
        disabled={createPost.isPending}
      >
        {createPost.isPending ? 'Creating...' : 'Create Post'}
      </button>
      
      {posts?.map(post => (
        <div key={post.id}>
          <h3>{post.title}</h3>
          <p>By: {post.author?.name}</p>
          <p>{post.content}</p>
        </div>
      ))}
    </div>
  );
}

🔒 Security Made Simple

Fastfold comes with built-in, declarative security that's both powerful and easy to use:

// 🔓 LEVEL 1: ZERO-SECURITY (0 lines of security code)
// Perfect for: public data, prototypes
blog: {
  schema: { title: 'string', content: 'string' },
  security: Security.public() // Everyone can access ✨
},

// 🔐 LEVEL 2: ONE-LINER SECURITY (1 line of security code)
// Perfect for: most common use cases
adminLogs: {
  schema: { action: 'string', timestamp: 'number' },
  security: Security.admin() // Only admins ✨
},

userProfiles: {
  schema: { userId: 'string', name: 'string', bio: 'string' },
  security: Security.owner('userId') // Users own their data ✨
},

// ⚙️ LEVEL 3: CUSTOM SECURITY (as complex as you need)
// Perfect for: complex business rules
projects: {
  schema: { name: 'string', teamId: 'string' },
  security: Security.custom((ctx) => {
    return ctx.user?.teamId === ctx.data?.teamId;
  })
}

API Reference

Server API

import Fastfold, { Security } from '@flavoai/fastfold';

// Quick start with Drizzle (recommended)
const adapter = await Fastfold.quickStart({
  drizzle: { db, schema },
  tables: {
    users: {
      security: Security.authenticated(),
      operations: ['read', 'update']
    },
    posts: {
      security: Security.owner('authorId'),
      operations: ['create', 'read', 'update', 'delete']
    }
  }
});

// Advanced configuration with custom endpoints
const adapter = await Fastfold.quickStart({
  drizzle: { db, schema },
  tables: { /* your table configs */ },
  auth: {
    secret: 'your-jwt-secret',
    expiresIn: '24h'
  },
  endpoints: (app) => {
    // Add custom endpoints
    app.get('/api/custom', (req, res) => {
      res.json({ message: 'Custom endpoint' });
    });
  },
  hooks: {
    onServerStart: async (server) => {
      console.log('Server started on port', server.port);
    }
  }
});

Client API

import { 
  useQuery,      // Fetch multiple records
  useQueryOne,   // Fetch single record
  useCreate,     // Create records
  useUpdate,     // Update records
  useDelete,     // Delete records
  configureFastfold,
  setAuthToken
} from '@flavoai/fastfold/client';

// Configure client
configureFastfold({ baseUrl: 'http://localhost:3001/api' });

// Set authentication
setAuthToken('your-jwt-token');

⚙️ Client Configuration

import { configureFastfold, setAuthToken } from '@flavoai/fastfold/client';

// Basic configuration
configureFastfold({
  baseUrl: 'https://api.myapp.com/api', // Your API base URL
  headers: {
    'X-App-Version': '1.0.0',
    'X-Custom-Header': 'value'
  }
});

// Authentication setup
function LoginComponent() {
  const handleLogin = async (email, password) => {
    // Your login logic here
    const response = await fetch('/auth/login', {
      method: 'POST',
      body: JSON.stringify({ email, password })
    });
    
    const { token } = await response.json();
    
    // Set auth token for all future requests
    setAuthToken(token);
    
    // Now all useQuery, useCreate, etc. will include this token
  };

  const handleLogout = () => {
    // Clear the auth token
    setAuthToken('');
    // or
    configureFastfold({ 
      headers: {} // Clear all headers
    });
  };

  return (
    <div>
      <button onClick={handleLogin}>Login</button>
      <button onClick={handleLogout}>Logout</button>
    </div>
  );
}

Complete Client Examples

📋 Querying Data

import { useQuery, useQueryOne } from '@flavoai/fastfold/client';

function PostsList() {
  // Fetch multiple posts with relationships
  const { data: posts, isLoading, error } = useQuery('posts', {
    where: { published: true },
    orderBy: { createdAt: 'desc' },
    with: { author: true }, // Include author data
    limit: 10
  });

  // Fetch single post by ID
  const { data: post } = useQueryOne('posts', '123', {
    with: { author: true, comments: true }
  });

  if (isLoading) return <div>Loading posts...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {posts?.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>By: {post.author?.name}</p>
          <p>{post.content}</p>
        </article>
      ))}
    </div>
  );
}

✏️ Creating Data

import { useCreate } from '@flavoai/fastfold/client';

function CreatePostForm() {
  const createPost = useCreate('posts', {
    onSuccess: (newPost) => {
      console.log('Post created:', newPost);
      // Cache automatically updated - no refetch needed!
    },
    onError: (error) => {
      console.error('Failed to create post:', error);
    }
  });

  const handleSubmit = async (formData) => {
    try {
      const newPost = await createPost.mutate({
        title: formData.title,
        content: formData.content,
        published: true,
        authorId: currentUser.id
      });
      
      console.log('Created post:', newPost);
    } catch (error) {
      console.error('Creation failed:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
      <button 
        type="submit" 
        disabled={createPost.isLoading}
      >
        {createPost.isLoading ? 'Creating...' : 'Create Post'}
      </button>
    </form>
  );
}

🔄 Updating Data

import { useUpdate } from '@flavoai/fastfold/client';

function EditPostForm({ post }) {
  const updatePost = useUpdate('posts', {
    onSuccess: (updatedPost) => {
      console.log('Post updated:', updatedPost);
      // Navigate back or show success message
    },
    onError: (error) => {
      console.error('Update failed:', error);
    }
  });

  const handleUpdate = async (formData) => {
    try {
      const updatedPost = await updatePost.mutate({
        id: post.id,
        data: {
          title: formData.title,
          content: formData.content,
          published: formData.published
        }
      });
      
      console.log('Updated post:', updatedPost);
    } catch (error) {
      console.error('Update failed:', error);
    }
  };

  return (
    <form onSubmit={handleUpdate}>
      {/* form fields */}
      <button 
        type="submit" 
        disabled={updatePost.isLoading}
      >
        {updatePost.isLoading ? 'Updating...' : 'Update Post'}
      </button>
      
      {updatePost.error && (
        <div className="error">
          Error: {updatePost.error.message}
        </div>
      )}
    </form>
  );
}

🗑️ Deleting Data

import { useDelete } from '@flavoai/fastfold/client';

function PostItem({ post, onDeleted }) {
  const deletePost = useDelete('posts', {
    onSuccess: () => {
      console.log('Post deleted successfully');
      onDeleted?.(post.id); // Notify parent component
    },
    onError: (error) => {
      console.error('Delete failed:', error);
    }
  });

  const handleDelete = async () => {
    if (confirm('Are you sure you want to delete this post?')) {
      try {
        await deletePost.mutate(post.id);
      } catch (error) {
        console.error('Delete failed:', error);
      }
    }
  };

  return (
    <div className="post-item">
      <h3>{post.title}</h3>
      <p>{post.content}</p>
      
      <button 
        onClick={handleDelete}
        disabled={deletePost.isLoading}
        className="delete-btn"
      >
        {deletePost.isLoading ? 'Deleting...' : 'Delete'}
      </button>
    </div>
  );
}

🔄 Complete CRUD Example

import { useQuery, useCreate, useUpdate, useDelete } from '@flavoai/fastfold/client';

function PostsManager() {
  // Query posts - React Query handles caching automatically
  const { data: posts, isLoading } = useQuery('posts', {
    with: { author: true },
    orderBy: { createdAt: 'desc' }
  });

  // CRUD operations - cache automatically invalidated!
  const createPost = useCreate('posts');
  const updatePost = useUpdate('posts');
  const deletePost = useDelete('posts');

  const handleCreate = (data) => createPost.mutate(data);
  const handleUpdate = (id, data) => updatePost.mutate({ id, data });
  const handleDelete = (id) => deletePost.mutate(id);

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      <CreateForm onSubmit={handleCreate} />
      
      {posts?.map(post => (
        <PostCard 
          key={post.id} 
          post={post}
          onUpdate={handleUpdate}
          onDelete={handleDelete}
        />
      ))}
    </div>
  );
}

🔄 Cache Management with React Query

Fastfold uses React Query for automatic cache management! Here's how it works:

Automatic Cache Invalidation (Default Behavior)
function PostsList() {
  // React Query handles all caching automatically!
  const { data: posts, isLoading, error } = useQuery('posts', {
    with: { author: true }
  });
  
  const createPost = useCreate('posts');
  const updatePost = useUpdate('posts');
  const deletePost = useDelete('posts');

  const handleCreate = () => {
    createPost.mutate({
      title: 'New Post',
      content: 'Hello World!'
    });
    // ✨ Cache automatically invalidated and refetched!
    // No manual refetch needed!
  };

  // React Query provides:
  // - 5min stale time with background refetch
  // - Automatic cache invalidation after mutations
  // - Smart deduplication of identical requests
  // - Error retry with exponential backoff
}
Advanced: Custom Cache Options
function PostsList() {
  // Customize React Query behavior
  const { data: posts } = useQuery('posts', {
    with: { author: true }
  }, {
    staleTime: 10 * 60 * 1000, // 10 minutes
    gcTime: 30 * 60 * 1000,    // 30 minutes  
    refetchOnWindowFocus: false,
    refetchInterval: 2 * 60 * 1000 // Poll every 2 minutes
  });

  const createPost = useCreate('posts', {
    onSuccess: (newPost) => {
      console.log('✅ Post created:', newPost);
      // Cache automatically updates - no manual work needed!
    },
    onError: (error) => {
      console.error('❌ Failed to create post:', error);
      // React Query handles retries automatically
    }
  });

  // React Query DevTools (optional)
  // Add <ReactQueryDevtools /> to see cache state
}
Advanced: Cache Utilities for Power Users
import { useInvalidateCache, useUpdateCache } from '@flavoai/fastfold/client';

function PostEditor() {
  const invalidateCache = useInvalidateCache();
  const updateCache = useUpdateCache();

  const updatePost = useUpdate('posts', {
    onSuccess: (updatedPost) => {
      // Option 1: Invalidate specific cache keys
      invalidateCache(['posts']); // Refetch all posts queries
      
      // Option 2: Update cache directly (optimistic)
      updateCache(['posts'], (oldData) => 
        oldData?.map(post => 
          post.id === updatedPost.id ? updatedPost : post
        )
      );
    }
  });

  const handleBulkUpdate = async () => {
    // For complex operations, invalidate multiple caches
    await bulkUpdatePosts();
    invalidateCache(['posts', 'users', 'stats']);
  };
}

✨ React Query Power: Fastfold uses React Query under the hood, giving you all its benefits:

  • Smart caching with configurable stale times
  • Background refetching to keep data fresh
  • Automatic deduplication of identical requests
  • Error retry with exponential backoff
  • Optimistic updates and rollback on error
  • DevTools integration for debugging cache state

Most apps won't need manual cache management - React Query handles it all automatically! 🎉

Security API

import { Security } from '@flavoai/fastfold/client';

Security.public()                    // Anyone can access
Security.admin()                     // Only admin role
Security.owner('userId')             // Owner-based access
Security.team('teamId')              // Team-based access
Security.authenticated()             // Any logged-in user
Security.readOnlyPublic()            // Public read, admin write
Security.custom((ctx) => boolean)    // Custom logic

Auto-Generated API Endpoints

Fastfold automatically creates CRUD endpoints based on your operations config:

Users (operations: ['read', 'update'])

GET    /api/users           ✅ List users
GET    /api/users/:id       ✅ Get user  
PUT    /api/users/:id       ✅ Update user
POST   /api/users           ❌ Blocked
DELETE /api/users/:id       ❌ Blocked

Posts (operations: ['create', 'read', 'update', 'delete'])

GET    /api/posts           ✅ List posts (with security filtering)
GET    /api/posts/:id       ✅ Get post
POST   /api/posts           ✅ Create post
PUT    /api/posts/:id       ✅ Update post (owner only)
DELETE /api/posts/:id       ✅ Delete post (owner only)

// Relationship endpoints (automatic from Drizzle relations)
GET    /api/posts/:id/author    ✅ Get post's author
GET    /api/posts/:id/comments  ✅ Get post's comments  
GET    /api/users/:id/posts     ✅ Get user's posts

Query Parameters

GET /api/posts?where={"published":true}&orderBy={"createdAt":"desc"}&limit=10
GET /api/posts?with={"author":true,"comments":true}  // Include relations

Examples

See the examples/ directory for complete examples:

Development

# Install dependencies
npm install

# Start development server
npm run dev

# Build for production
npm run build

# Run tests
npm test

Database Operations

Fastfold automatically handles database setup with zero configuration:

# Auto-migration: Tables are created automatically when server starts
npm run dev  # Creates tables from your schema definitions

# Reset database: Simply delete the database file
rm ./fastfold.db  # Default SQLite database file
npm run dev       # Recreates tables on next start

# Custom database location
const adapter = await Fastfold.quickStart({
  drizzle: { 
    db: drizzle(new Database('./my-app.db'), { schema }),
    schema 
  },
  tables: { /* your table configs */ }
});

Note: Fastfold uses auto-migration - tables are created/updated automatically based on your schema definitions. No manual migration commands needed.

Advanced Features

🔒 Granular CRUD Control

Control exactly which operations are allowed per table:

await Fastfold.quickStart({
  drizzle: { db, schema },
  tables: {
    users: {
      security: Security.authenticated(),
      operations: ['read', 'update'] // Only GET and PUT endpoints
    },
    posts: {
      security: Security.owner('authorId'),
      operations: ['create', 'read', 'update', 'delete'] // Full CRUD
    },
    comments: {
      security: Security.custom((ctx) => ctx.user?.id === ctx.data?.authorId),
      operations: ['create', 'read', 'delete'] // No updates allowed
    }
  }
});

🎯 Custom Endpoints with Full Drizzle Power

const adapter = await Fastfold.quickStart({
  drizzle: { db, schema },
  tables: { /* your table configs */ },
  
  endpoints: (app) => {
    // Use Drizzle's relational queries
    app.get('/api/trending', Security.public(), async (req, res) => {
      const trending = await adapter.queryWithRelations('posts', {
        with: { 
          author: { columns: { name: true } },
          comments: true 
        },
        where: { published: true },
        orderBy: { createdAt: 'desc' },
        limit: 10
      });
      res.json(trending);
    });

    // Complex business logic
    app.post('/api/newsletter', Security.admin(), async (req, res) => {
      const users = await adapter.query('users', {
        where: { role: 'subscriber' }
      });
      // Send emails...
      res.json({ sent: users.length });
    });
  }
});

// Use adapter anywhere in your app
export { adapter };

🚀 Advanced Configuration

await Fastfold.quickStart({
  drizzle: { db, schema },
  tables: { /* your table configs */ },
  
  // Authentication configuration
  auth: {
    secret: 'your-jwt-secret',
    expiresIn: '24h'
  },
  
  // Custom endpoints
  endpoints: (app) => {
    app.get('/api/trending', async (req, res) => {
      const trending = await adapter.queryWithRelations('posts', {
        with: { author: true },
        where: { published: true },
        orderBy: { createdAt: 'desc' },
        limit: 10
      });
      res.json(trending);
    });
  },
  
  // Server hooks
  hooks: {
    onServerStart: async (server) => {
      console.log('🚀 Server ready on port', server.port);
    }
  }
});
});

Why Fastfold?

Perfect for LLMs and rapid development:

  1. Drizzle ORM Power - Full database relationships, migrations, validation
  2. Tiny API surface - Just define Drizzle schema + security rules
  3. Zero boilerplate - Auto-generated CRUD with granular control
  4. Type-safe by default - Full TypeScript from database to frontend
  5. Production ready - PostgreSQL, MySQL, SQLite with foreign keys
  6. Extensible - Full Express access + Drizzle queries when needed

Perfect for:

Production applications - Full database power with Drizzle
Rapid prototyping - Zero-config CRUD generation
LLM-assisted development - Minimal, predictable API
React + Backend - Unified TypeScript experience
Complex data models - Relationships, constraints, validation
Custom business logic - Express endpoints + Drizzle queries

Not suitable for:

Microservices architecture - Single server design
Non-database apps - Focused on CRUD operations
GraphQL requirements - REST API only
Real-time by default - WebSockets require custom endpoints

License

MIT