# 1. Start database
cd docker && docker-compose up -d
# 2. Run migrations (first time only)
pnpm --filter @redstone/database db:migrate
# 3. Start dev server
pnpm dev:web# Stop and remove containers
cd docker && docker-compose down -v
# Start fresh
docker-compose up -d
# Wait a moment, then run migrations
pnpm --filter @redstone/database db:migrate
pnpm --filter @redstone/database db:seed# Open Prisma Studio
pnpm --filter @redstone/database db:studio
# Opens at http://localhost:5555# After modifying schema.prisma
pnpm --filter @redstone/database db:migrate
# You'll be prompted for a migration name# After schema changes
pnpm --filter @redstone/database db:generate# Login
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@redstone.app","password":"password123"}' \
| jq -r '.token'
# Save token to variable
export TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@redstone.app","password":"password123"}' \
| jq -r '.token')# List files
curl -X GET http://localhost:3000/api/files \
-H "Authorization: Bearer $TOKEN" | jq
# Create file
curl -X POST http://localhost:3000/api/files \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"My Note","content":"# Hello"}' | jq
# Search
curl -X GET "http://localhost:3000/api/search?q=hello" \
-H "Authorization: Bearer $TOKEN" | jq
# List folders
curl -X GET http://localhost:3000/api/folders \
-H "Authorization: Bearer $TOKEN" | jq// Use the useAuth hook in client components
import { useAuth } from '@/lib/hooks/use-auth';
function MyComponent() {
const { user, isAuthenticated, isLoading, signOut } = useAuth();
if (isLoading) return <div>Loading...</div>;
if (!isAuthenticated) return <div>Please sign in</div>;
return <div>Welcome, {user?.email}</div>;
}// Use SWR hooks for data fetching
import { useFiles } from '@/lib/hooks/use-files';
import { useFolders } from '@/lib/hooks/use-folders';
function FileList() {
const { files, isLoading, mutate } = useFiles();
// files are automatically fetched with authentication
}// For mutations (create, update, delete)
import { filesApi } from '@/lib/api-client';
// Create a file
const newFile = await filesApi.create({
title: 'My Note',
content: '# Hello',
});
// Update a file
await filesApi.update(fileId, {
title: 'Updated Title',
content: 'Updated content',
});
// Delete a file
await filesApi.delete(fileId);- Create route file in
apps/web/app/api/[your-endpoint]/route.ts - Import required utilities:
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@redstone/database';
import { getUserId } from '@/lib/api-middleware';- Implement handler:
export async function GET(request: NextRequest) {
const userId = await getUserId(request);
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Your logic here
return NextResponse.json({ data: 'your data' });
}- Add corresponding function to
lib/api-client.tsif needed - Add types to
lib/types.tsfor request/response - Create SWR hook in
lib/hooks/if it's a data-fetching endpoint
All API types are centralized in apps/web/lib/types.ts:
// Import types
import type { File, Folder, Tag, FilesListResponse } from '@/lib/types';
// Use in components
const { files } = useFiles(); // files is File[]
// Use in API client functions
const response = await filesApi.list(); // FilesListResponseKey types available:
- Database models:
User,File,Folder,Tag,FileVersion - API responses:
FilesListResponse,FileResponse,FoldersListResponse, etc. - Request payloads:
CreateFileRequest,UpdateFileRequest, etc.
Best practices:
- Never use
any- always define proper types - API responses should have dedicated response types
- Request payloads should have dedicated request types
- Update
lib/types.tswhen adding new API endpoints
- Route:
/auth/signin - File:
apps/web/app/auth/signin/page.tsx - Uses NextAuth credentials provider
- Automatically redirects authenticated users to home
- Route:
/auth/signup - File:
apps/web/app/auth/signup/page.tsx - Creates new user account
- Automatically signs in after registration
# Edit packages/shared/src/index.ts
# Types are automatically available in all apps# 1. Edit packages/database/prisma/schema.prisma
# 2. Generate Prisma client
pnpm --filter @redstone/database db:generate
# 3. Create migration
pnpm --filter @redstone/database db:migrate# Next.js dev server logs (already visible in terminal)
# Database logs
docker logs docker-postgres-1 -f# Connect to PostgreSQL directly
docker exec -it docker-postgres-1 psql -U redstone -d redstone
# List tables
\dt
# Query users
SELECT * FROM users;
# Exit
\qSet in .env.local:
# Enable query logging
DATABASE_URL="postgresql://redstone:password@localhost:5432/redstone?connection_limit=5&socket_timeout=3"
# Find process on port 3000
lsof -i :3000
# Kill it
kill -9 <PID># Check if PostgreSQL is running
docker ps | grep postgres
# Restart database
cd docker && docker-compose restart postgres
# Check logs
docker logs docker-postgres-1# Regenerate Prisma client
pnpm --filter @redstone/database db:generate
# Restart Next.js dev server# Reinstall dependencies
pnpm install
# Clear Next.js cache
rm -rf apps/web/.next
# Restart dev server
pnpm dev:web# Check environment variables
# Ensure .env.local has:
# NEXTAUTH_SECRET=your-secret-key
# NEXTAUTH_URL=http://localhost:3000
# DATABASE_URL=postgresql://...
# Clear NextAuth cookies
# In browser DevTools: Application > Cookies > Delete all localhost cookies
# Check if user exists in database
pnpm --filter @redstone/database db:studio
# Navigate to users table
# Test authentication endpoint
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@redstone.app","password":"password123"}'// Success (200/201)
{
"data": { ... }, // Single item
"items": [ ... ], // List of items
"pagination": { ... } // For paginated lists
}
// Error (4xx/5xx)
{
"error": "Error message"
}try {
// Your logic
return NextResponse.json({ data });
} catch (error) {
console.error('Error description:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}const userId = await getUserId(request);
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}- Use indexes: Add
@@index()to frequently queried fields in Prisma schema - Pagination: Always paginate large lists
- Select specific fields: Use Prisma
selectto only fetch needed data - Batch operations: Use
Promise.all()for parallel operations - Soft deletes: Filter
deletedAt: nullin all queries
- ✅ All API routes check authentication
- ✅ User ID validated from token/session
- ✅ Passwords are hashed with bcrypt
- ✅ SQL injection prevented by Prisma
- ✅ Input validation on all endpoints
- ✅ HTTP-only cookies for web auth
- ✅ Soft deletes for data recovery