Understanding Generated Code
Deep dive into what DataBridge generates and how it all works together
Understanding Generated Code
Learn what DataBridge creates when you run databridge generate and how each piece fits together.
Overview
When you run databridge generate, DataBridge analyzes your Prisma schema and generates:
- ✅ OpenAPI 3.0 Specification - Industry-standard API documentation
- ✅ Swagger UI Plugin - Interactive API testing at
/docs - ✅ Fastify API routes with full CRUD operations
- ✅ Zod validation schemas
- ✅ Prisma plugin for database access
- ✅ Server configuration with auto-port selection
- ✅ Test scripts for validation
- ✅ TypeScript type definitions
Learn more: OpenAPI & Swagger UI Tutorial
File Structure
After running databridge generate in a project, you’ll have:
my-project/
├── apps/
│ └── api/
│ └── src/
│ ├── openapi.json # OpenAPI 3.0 specification
│ ├── routes/
│ │ ├── _generated.ts # Route registry
│ │ ├── users.ts # User CRUD routes
│ │ ├── products.ts # Product CRUD routes
│ │ └── orders.ts # Order CRUD routes
│ ├── plugins/
│ │ ├── prisma.ts # Database plugin
│ │ └── swagger.ts # Swagger UI plugin
│ ├── utils/
│ │ └── logger.ts # Logging utility
│ └── server.ts # Main server file
├── prisma/
│ └── schema.prisma # Database schema
├── test-api.sh # Automated test suite
└── package.json
Route Files
Each database table gets its own route file with full CRUD operations.
Example: routes/products.ts
import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
import { z } from "zod";
import { prisma } from "../plugins/prisma";
// Zod validation schema
const productsSchema = z.object({
name: z.string().min(1, "Name is required"),
description: z.string().optional(),
price: z.number().positive("Price must be positive"),
stock: z.number().int().nonnegative("Stock cannot be negative").optional(),
category: z.string().max(100).optional(),
is_active: z.boolean().optional(),
});
export default async function productsRoutes(fastify: FastifyInstance) {
// GET /products - List all products
fastify.get('/products', async (request: FastifyRequest, reply: FastifyReply) => {
try {
const items = await prisma.products.findMany();
return reply.send(items);
} catch (error: any) {
request.log.error(error);
return reply.status(500).send({
error: 'Internal server error',
message: error.message
});
}
});
// GET /products/:id - Get single product
fastify.get<{ Params: { id: string } }>(
'/products/:id',
async (request, reply) => {
try {
const id = parseInt(request.params.id);
if (isNaN(id)) {
return reply.status(400).send({
error: 'Invalid ID format',
message: 'ID must be a number'
});
}
const item = await prisma.products.findUnique({ where: { id } });
if (!item) {
return reply.status(404).send({
error: 'products not found'
});
}
return reply.send(item);
} catch (error: any) {
request.log.error(error);
return reply.status(500).send({
error: 'Internal server error',
message: error.message
});
}
}
);
// POST /products - Create new product
fastify.post('/products', async (request: FastifyRequest, reply: FastifyReply) => {
try {
// Validate request body
const validatedData = productsSchema.parse(request.body);
const item = await prisma.products.create({
data: validatedData as any,
});
return reply.status(201).send(item);
} catch (error: any) {
if (error.name === 'ZodError') {
return reply.status(400).send({
error: 'Validation failed',
issues: error.errors
});
}
request.log.error(error);
return reply.status(500).send({
error: 'Internal server error',
message: error.message
});
}
});
// PATCH /products/:id - Update product
fastify.patch<{ Params: { id: string } }>(
'/products/:id',
async (request, reply) => {
try {
const id = parseInt(request.params.id);
if (isNaN(id)) {
return reply.status(400).send({
error: 'Invalid ID format',
message: 'ID must be a number'
});
}
// Validate request body (partial update)
const validatedData = productsSchema.partial().parse(request.body);
const item = await prisma.products.update({
where: { id },
data: validatedData as any,
});
return reply.send(item);
} catch (error: any) {
if (error.name === 'ZodError') {
return reply.status(400).send({
error: 'Validation failed',
issues: error.errors
});
}
if (error.code === 'P2025') {
return reply.status(404).send({
error: 'products not found'
});
}
request.log.error(error);
return reply.status(500).send({
error: 'Internal server error',
message: error.message
});
}
}
);
// DELETE /products/:id - Delete product
fastify.delete<{ Params: { id: string } }>(
'/products/:id',
async (request, reply) => {
try {
const id = parseInt(request.params.id);
if (isNaN(id)) {
return reply.status(400).send({
error: 'Invalid ID format',
message: 'ID must be a number'
});
}
await prisma.products.delete({ where: { id } });
return reply.status(204).send();
} catch (error: any) {
if (error.code === 'P2025') {
return reply.status(404).send({
error: 'products not found'
});
}
request.log.error(error);
return reply.status(500).send({
error: 'Internal server error',
message: error.message
});
}
}
);
}
What’s Happening Here?
1. Zod Validation Schema:
const productsSchema = z.object({
name: z.string().min(1, "Name is required"),
price: z.number().positive("Price must be positive"),
// ...
});
- Defines validation rules for your data
- Auto-generated based on Prisma schema types
- Provides clear error messages
2. Error Handling:
catch (error: any) {
if (error.name === 'ZodError') {
return reply.status(400).send({ /* validation error */ });
}
if (error.code === 'P2025') {
return reply.status(404).send({ /* not found */ });
}
return reply.status(500).send({ /* server error */ });
}
- 400: Validation errors (bad input)
- 404: Resource not found
- 500: Server/database errors
3. Type Safety:
fastify.get<{ Params: { id: string } }>('/products/:id', ...)
- TypeScript types ensure compile-time safety
- Request params, body, query are all typed
Prisma Plugin
The Prisma plugin provides database access to all routes.
plugins/prisma.ts
import { FastifyPluginAsync } from 'fastify';
import { PrismaClient } from '@prisma/client';
import fp from 'fastify-plugin';
const prismaClient = new PrismaClient({
log: ['query', 'error', 'warn'],
});
declare module 'fastify' {
interface FastifyInstance {
prisma: PrismaClient;
}
}
const prismaPlugin: FastifyPluginAsync = async (fastify) => {
fastify.decorate('prisma', prismaClient);
fastify.addHook('onClose', async (instance) => {
await instance.prisma.$disconnect();
});
};
export default fp(prismaPlugin);
export const prisma = prismaClient;
Key Features:
- Singleton Pattern: One Prisma client for entire app
- Logging: Logs queries, errors, and warnings
- Graceful Shutdown: Disconnects on server close
- Global Export:
prismacan be imported anywhere
Server Configuration
The main server file ties everything together.
server.ts
import Fastify from 'fastify';
import cors from '@fastify/cors';
import prismaPlugin from './plugins/prisma';
import usersRoutes from './routes/users';
import productsRoutes from './routes/products';
import ordersRoutes from './routes/orders';
const host = process.env.HOST || '0.0.0.0';
let port = parseInt(process.env.API_PORT || '3000', 10);
async function start() {
const fastify = Fastify({
logger: {
transport: {
target: 'pino-pretty',
options: {
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
},
},
});
// Register plugins
await fastify.register(cors, { origin: '*' });
await fastify.register(prismaPlugin);
// Register routes
await fastify.register(usersRoutes);
await fastify.register(productsRoutes);
await fastify.register(ordersRoutes);
// Health check
fastify.get('/health', async () => {
const dbStatus = await fastify.prisma.$queryRaw`SELECT 1`;
return {
status: 'ok',
database: dbStatus ? 'connected' : 'disconnected',
timestamp: new Date().toISOString()
};
});
// Metrics
fastify.get('/metrics', async () => {
const memory = process.memoryUsage();
return {
uptime: process.uptime(),
memory: {
rss: `${Math.round(memory.rss / 1024 / 1024)} MB`,
heapUsed: `${Math.round(memory.heapUsed / 1024 / 1024)} MB`,
},
timestamp: new Date().toISOString(),
};
});
// Auto port selection (tries 3000-3004)
const maxPort = port + 4;
while (port <= maxPort) {
try {
await fastify.listen({ port, host });
console.log(`✅ Server running on http://${host}:${port}`);
break;
} catch (error: any) {
if (error.code === 'EADDRINUSE' && port < maxPort) {
console.log(`Port ${port} in use, trying ${port + 1}...`);
port++;
continue;
}
throw error;
}
}
// Graceful shutdown
const signals = ['SIGINT', 'SIGTERM'];
signals.forEach((signal) => {
process.on(signal, async () => {
console.log(`\n${signal} received, closing server...`);
await fastify.close();
process.exit(0);
});
});
}
start().catch((err) => {
console.error('Failed to start server:', err);
process.exit(1);
});
Key Features:
- Auto Port Selection: Tries ports 3000-3004 automatically
- Structured Logging: Pino with pretty formatting
- CORS Enabled: Allows frontend access
- Health Check:
/healthendpoint with DB status - Metrics:
/metricsendpoint for monitoring - Graceful Shutdown: Clean database disconnect
Validation with Zod
How It Works
DataBridge generates Zod schemas based on your Prisma types:
Prisma Schema:
model products {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
price Decimal @db.Decimal(10, 2)
stock Int? @default(0)
is_active Boolean? @default(true)
}
Generated Zod Schema:
const productsSchema = z.object({
name: z.string().min(1).max(255),
price: z.number().positive(),
stock: z.number().int().nonnegative().optional(),
is_active: z.boolean().optional(),
});
Validation Error Example
Request:
curl -X POST http://localhost:3000/products \
-H "Content-Type: application/json" \
-d '{"name": "", "price": -10}'
Response:
{
"error": "Validation failed",
"issues": [
{
"path": ["name"],
"message": "Name is required"
},
{
"path": ["price"],
"message": "Price must be positive"
}
]
}
Test Script
DataBridge generates a comprehensive test suite.
test-api.sh (excerpt)
#!/bin/bash
BASE_URL="http://localhost:3000"
echo "========================================="
echo "DataBridge API Test Suite"
echo "========================================="
# Test: Health Check
echo "Test: Health Check"
RESPONSE=$(curl -s ${BASE_URL}/health)
echo $RESPONSE
if echo "$RESPONSE" | grep -q "ok"; then
echo "✅ Pass"
else
echo "❌ Fail"
fi
# Test: Create Product
echo "Test: POST /products (Create valid)"
RESPONSE=$(curl -s -X POST ${BASE_URL}/products \
-H "Content-Type: application/json" \
-d '{"name": "Test Product", "price": 99.99}')
if echo "$RESPONSE" | grep -q "id"; then
echo "✅ Pass"
else
echo "❌ Fail"
fi
# Test: Validation Error
echo "Test: POST /products (Negative price)"
RESPONSE=$(curl -s -X POST ${BASE_URL}/products \
-H "Content-Type: application/json" \
-d '{"name": "Test", "price": -10}')
if echo "$RESPONSE" | grep -q "Validation failed"; then
echo "✅ Pass - Validation caught negative price"
else
echo "❌ Fail"
fi
HTTP Status Codes
DataBridge uses standard HTTP status codes:
| Code | Meaning | When Used |
|---|---|---|
| 200 | OK | Successful GET, PATCH |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Validation error, invalid ID format |
| 404 | Not Found | Resource doesn’t exist |
| 409 | Conflict | Unique constraint violation |
| 500 | Server Error | Database error, unexpected error |
Customizing Generated Code
Adding Custom Routes
You can add custom business logic alongside generated routes:
// routes/products-custom.ts
export default async function customProductsRoutes(fastify: FastifyInstance) {
// Custom route: Search products
fastify.get('/products/search', async (request, reply) => {
const { q } = request.query as { q: string };
const results = await fastify.prisma.products.findMany({
where: {
OR: [
{ name: { contains: q } },
{ description: { contains: q } },
],
},
});
return results;
});
// Custom route: Low stock products
fastify.get('/products/low-stock', async (request, reply) => {
const results = await fastify.prisma.products.findMany({
where: {
stock: { lt: 10 },
},
orderBy: {
stock: 'asc',
},
});
return results;
});
}
Register in server.ts:
import customProductsRoutes from './routes/products-custom';
await fastify.register(customProductsRoutes);
Extending Validation
Add custom validation rules:
const productsSchema = z.object({
name: z.string().min(1).max(255),
price: z.number().positive(),
stock: z.number().int().nonnegative().optional(),
category: z.enum(['electronics', 'clothing', 'food']), // Custom enum
sku: z.string().regex(/^[A-Z]{3}-\d{6}$/), // Custom pattern
});
Best Practices
DO ✅
- Keep generated files unmodified when possible
- Add custom logic in separate files
- Use environment variables for configuration
- Test all endpoints with
test-api.sh - Review Zod schemas for your business rules
DON’T ❌
- Modify
_generated.ts(it’s regenerated) - Hard-code database URLs in code
- Skip validation on POST/PATCH routes
- Ignore TypeScript errors
- Commit
.envfiles
Regenerating Code
If you modify your database schema:
# 1. Update database
mysql -h localhost -u root -ppassword mydb < updates.sql
# 2. Re-introspect
databridge introspect
# 3. Regenerate code
databridge generate
Note: DataBridge will overwrite generated files. Save any custom changes to separate files first!
Additional Resources
Was this page helpful?
Thank you for your feedback!