Development Workflow
This guide covers the recommended development workflow for Vinoflare projects, from initial setup to deployment.
Initial Setup
1. Create Your Project
npm create vinoflare@latest my-app
Follow the interactive prompts to choose your template and features.
2. Environment Setup
After creation, set up your environment variables:
# Copy example file
cp .dev.vars.example .dev.vars
# Edit with your values
code .dev.vars
Required variables for auth-enabled templates:
BETTER_AUTH_SECRET=your-secret-key
DISCORD_CLIENT_ID=your-discord-client-id
DISCORD_CLIENT_SECRET=your-discord-client-secret
3. Database Setup (if applicable)
# Generate initial migration
npm run db:generate
# Apply to local database
npm run db:push:local
# Generate TypeScript types
npm run gen:types
Development Server
Start Everything
This starts:
- 🚀 Vite dev server (port 5173)
- 🔧 Wrangler dev server (port 8787)
- 👀 File watchers for type generation
Access Points
- Frontend: http://localhost:5173
- API: http://localhost:8787
- API Docs: http://localhost:8787/docs
Core Development Flow
1. API Development (Backend First)
Create a New Module
This generates:
Define the Schema
// posts.table.ts
export const posts = sqliteTable("posts", {
id: text("id").primaryKey(),
title: text("title").notNull(),
content: text("content").notNull(),
authorId: text("author_id").notNull(),
published: integer("published", { mode: "boolean" }).default(false),
createdAt: integer("created_at", { mode: "timestamp" }),
});
Implement Handlers
// posts.handlers.ts
export async function createPost(c: Context) {
const user = c.get("user");
const data = c.req.valid("json");
const post = await db.insert(posts).values({
...data,
authorId: user.id,
}).returning();
return c.json(post[0], 201);
}
2. Type Generation
After creating API endpoints:
# Generate OpenAPI schema
npm run gen:openapi
# For Orval templates
npm run gen:api
# For RPC templates
npm run gen:client
3. Frontend Development
Create Routes
// src/client/routes/posts/index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { usePosts } from "@/client/hooks/use-posts";
export const Route = createFileRoute("/posts/")({
component: PostsPage,
});
function PostsPage() {
const { data: posts, isLoading } = usePosts();
if (isLoading) return <div>Loading...</div>;
return (
<div className="container py-8">
<h1>Posts</h1>
{/* Your UI */}
</div>
);
}
Update Navigation
# Regenerate route tree
npm run gen:routes
Testing Strategy
1. Unit Tests (Vitest)
// posts.test.ts
describe("Posts API", () => {
it("should create a post", async () => {
const app = createApp();
const res = await app.request("/api/posts", {
method: "POST",
json: { title: "Test", content: "Content" },
});
expect(res.status).toBe(201);
});
});
Run tests:
2. Integration Tests
describe("Posts Integration", () => {
beforeEach(async () => {
await resetDatabase();
});
it("should handle full post lifecycle", async () => {
// Test create, read, update, delete
});
});
3. E2E Tests (Optional)
# Install Playwright
npm install -D @playwright/test
# Run E2E tests
npm run test:e2e
Database Migrations
Create Migration
When you modify schema:
This creates a SQL migration file in drizzle/
directory.
Apply Migrations
# Local development
npm run db:push:local
# Production
npm run db:push:prod
Rollback (if needed)
# Create down migration
npm run db:generate -- --custom
# Apply specific migration
wrangler d1 execute DB --file=./drizzle/0001_rollback.sql
Git Workflow
1. Feature Branch
git checkout -b feature/add-comments
2. Commit Often
git add .
git commit -m "feat: add comment model and API endpoints"
3. Before Push
# Run linting
npm run lint
# Run type checking
npm run typecheck
# Run tests
npm test
# Build to verify
npm run build
4. Pull Request
Create PR with:
- Clear description
- Link to issue (if applicable)
- Screenshots (for UI changes)
- Test coverage
Debugging
Backend Debugging
// Add console logs (visible in terminal)
console.log("Debug:", data);
// Use Wrangler logs
wrangler tail
Frontend Debugging
- React DevTools
- Browser DevTools
- Network tab for API calls
Database Debugging
# Open D1 console
wrangler d1 execute DB --command="SELECT * FROM posts"
# Use Drizzle Studio (if available)
npm run db:studio
1. API Optimization
// Use select specific columns
const posts = await db
.select({
id: posts.id,
title: posts.title,
})
.from(posts);
// Add indexes
sqliteTable("posts", {
// ...
}, (table) => ({
authorIdx: index("author_idx").on(table.authorId),
}));
2. Frontend Optimization
// Lazy load routes
const PostsRoute = lazy(() => import("./routes/posts"));
// Optimize queries
const { data } = useQuery({
queryKey: ["posts", { page }],
queryFn: fetchPosts,
staleTime: 5 * 60 * 1000, // 5 minutes
});
Deployment Preparation
1. Environment Check
# Verify all secrets are set
wrangler secret list
# Test production build
npm run build
npm run preview
2. Database Migration
# Apply migrations to production
npm run db:push:prod
3. Deploy
Common Issues & Solutions
Types Not Updating
# Restart TS server
# In VS Code: Cmd+Shift+P -> "TypeScript: Restart TS Server"
# Clear cache and regenerate
rm -rf node_modules/.cache
npm run gen:types
Database Connection Issues
# Verify D1 binding
wrangler d1 list
# Check wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "your-db-name"
database_id = "your-db-id"
Build Failures
# Clear all caches
rm -rf node_modules/.vite
rm -rf dist
npm run build
Best Practices
- Commit generated files - Keep
generated/
in git
- Run generators before commit - Ensure types are in sync
- Test before push - Run full test suite
- Use conventional commits - feat:, fix:, chore:
- Document API changes - Update OpenAPI descriptions
- Handle errors gracefully - Both frontend and backend
- Optimize for production - Check bundle size
Next Steps