lyzr-architect
v0.1.2
Published
Per-app MongoDB database layer with RLS, auth scaffolding, and React components for Architect apps
Readme
lyzr-architect
Per-app MongoDB database layer with row-level security, authentication, and React components for Architect apps.
Features
- MongoDB connection with DocumentDB compatibility
- Row-level security (RLS) — Mongoose plugin auto-scopes all queries by
owner_user_id - Auth scaffolding — email/password registration, login, JWT sessions
- React components — AuthProvider, LoginForm, RegisterForm, ProtectedRoute, UserMenu
- Pre-built API routes — mount
/api/auth/*with one line each
Install
npm install lyzr-architectEnvironment Variables
These are automatically injected into the sandbox by the backend:
DATABASE_URL=mongodb://user:pass@host:port/db
APP_JWT_SECRET=<random-secret>
DATABASE_NAME=app_myapp
DATABASE_PROVIDER=mongodbQuick Start
1. Initialize the database
// lib/db.ts
import { initDB } from 'lyzr-architect';
// Connects to MongoDB using DATABASE_URL, registers RLS plugin globally.
// Idempotent — safe to call multiple times.
await initDB();2. Define models
// models/task.ts
import { createModel } from 'lyzr-architect';
// createModel auto-adds: owner_user_id (for RLS), created_at, updated_at
// Do NOT add these fields manually.
const Task = createModel('Task', {
title: { type: String, required: true },
description: { type: String, default: '' },
status: { type: String, enum: ['todo', 'in_progress', 'done'], default: 'todo' },
due_date: { type: Date },
});
export default Task;3. Mount auth routes
// app/api/auth/register/route.ts
import { handleRegister } from 'lyzr-architect';
export const POST = handleRegister;
// app/api/auth/login/route.ts
import { handleLogin } from 'lyzr-architect';
export const POST = handleLogin;
// app/api/auth/logout/route.ts
import { handleLogout } from 'lyzr-architect';
export const POST = handleLogout;
// app/api/auth/me/route.ts
import { handleMe } from 'lyzr-architect';
export const GET = handleMe;4. Protect API routes
// app/api/tasks/route.ts
import { authMiddleware, initDB } from 'lyzr-architect';
import Task from '@/models/task';
// authMiddleware extracts JWT from cookie/header, sets RLS context.
// All queries are automatically scoped to the current user.
export const GET = authMiddleware(async (req) => {
await initDB();
const tasks = await Task.find(); // Auto-filtered by owner_user_id
return Response.json({ tasks });
});
export const POST = authMiddleware(async (req) => {
await initDB();
const body = await req.json();
const task = await Task.create({ title: body.title, status: body.status });
// owner_user_id is auto-set to the current user
return Response.json({ task });
});For public routes that optionally show user data, use optionalAuth:
import { optionalAuth, initDB } from 'lyzr-architect';
export const GET = optionalAuth(async (req) => {
await initDB();
if (req.userId) {
// Logged in — return user-specific data
} else {
// Not logged in — return public data
}
});5. Add React components
// app/layout.tsx
import { AuthProvider } from 'lyzr-architect/client';
export default function RootLayout({ children }) {
return (
<html>
<body>
<AuthProvider>{children}</AuthProvider>
</body>
</html>
);
}// app/login/page.tsx
'use client';
import { LoginForm, RegisterForm } from 'lyzr-architect/client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function LoginPage() {
const [isRegister, setIsRegister] = useState(false);
const router = useRouter();
if (isRegister) {
return (
<RegisterForm
onSuccess={() => router.push('/dashboard')}
onSwitchToLogin={() => setIsRegister(false)}
/>
);
}
return (
<LoginForm
onSuccess={() => router.push('/dashboard')}
onSwitchToRegister={() => setIsRegister(true)}
/>
);
}// app/dashboard/page.tsx
'use client';
import { ProtectedRoute, UserMenu, useAuth } from 'lyzr-architect/client';
export default function Dashboard() {
const { user } = useAuth();
return (
<ProtectedRoute>
<header>
<h1>Dashboard</h1>
<UserMenu />
</header>
<p>Welcome, {user?.name || user?.email}</p>
</ProtectedRoute>
);
}API Reference
Server-side (lyzr-architect)
| Export | Description |
|--------|-------------|
| initDB() | Connect to MongoDB and register RLS plugin. Call before using models. |
| createModel(name, schema) | Create a Mongoose model with auto owner_user_id, created_at, updated_at. |
| authMiddleware(handler) | Wrap API route — requires auth, sets RLS context. Returns 401 if no token. |
| optionalAuth(handler) | Wrap API route — sets RLS context if token present, continues without if not. |
| handleRegister | POST /api/auth/register — { email, password, name? } |
| handleLogin | POST /api/auth/login — { email, password } |
| handleLogout | POST /api/auth/logout |
| handleMe | GET /api/auth/me — returns current user or { user: null } |
| signToken(payload) | Sign a JWT with APP_JWT_SECRET |
| verifyToken(token) | Verify and decode a JWT |
| hashPassword(password) | bcrypt hash |
| verifyPassword(password, hash) | bcrypt compare |
| UserModel | Mongoose model for _users collection (system collection, RLS-exempt) |
| rlsPlugin | The Mongoose global plugin (registered automatically by initDB()) |
| runWithContext(ctx, fn) | Run a function with a specific RLS context |
| getCurrentUserId() | Get the current user ID from RLS context |
Client-side (lyzr-architect/client)
| Export | Description |
|--------|-------------|
| <AuthProvider> | React context provider. Wrap your app with this. Props: basePath? (default: /api/auth) |
| <LoginForm> | Login form. Props: onSuccess?, onSwitchToRegister?, className? |
| <RegisterForm> | Registration form. Props: onSuccess?, onSwitchToLogin?, className? |
| <ProtectedRoute> | Auth guard. Props: loadingFallback?, unauthenticatedFallback?, loginPath? |
| <UserMenu> | User avatar dropdown with logout. Props: className? |
| useAuth() | Hook returning { user, isLoading, login, register, logout, refreshUser } |
How RLS Works
The RLS plugin is a Mongoose global plugin that:
- Adds
owner_user_idfield to every model (except system collections) - Auto-filters all queries (
find,findOne,update,delete, etc.) byowner_user_id - Auto-sets
owner_user_idonsaveandinsertMany - Blocks direct access to system collections (
_users,_sessions)
This means User A can never see User B's data, even if the app code doesn't filter by user.
DocumentDB Compatibility
The package is compatible with both MongoDB and AWS DocumentDB:
- Uses
retryWrites: false - Standard
mongodb://connection strings only (nomongodb+srv://) - Standard query operators only (no
$expr, no Map-Reduce) - RLS plugin uses
where()conditions (maps to$and)
License
MIT
