Initial commit
This commit is contained in:
@@ -0,0 +1,373 @@
|
||||
# Full-Stack Application Scaffold Example
|
||||
|
||||
Complete monorepo with React frontend (TanStack) and Cloudflare Worker backend with shared database.
|
||||
|
||||
**Duration**: 30 min | **Files**: 35 | **LOC**: ~850 | **Stack**: React + Vite + TanStack + Cloudflare Worker + D1
|
||||
|
||||
---
|
||||
|
||||
## Monorepo Structure
|
||||
|
||||
```
|
||||
my-fullstack-app/
|
||||
├── frontend/ # React + Vite + TypeScript
|
||||
│ ├── src/
|
||||
│ │ ├── main.tsx # Entry point
|
||||
│ │ ├── routes/ # TanStack Router routes
|
||||
│ │ ├── components/ # React components
|
||||
│ │ ├── services/ # API client
|
||||
│ │ └── lib/ # Utilities
|
||||
│ ├── tests/
|
||||
│ ├── package.json
|
||||
│ ├── vite.config.ts
|
||||
│ └── tsconfig.json
|
||||
├── backend/ # Cloudflare Worker
|
||||
│ ├── src/
|
||||
│ │ ├── index.ts # Hono app
|
||||
│ │ ├── routes/ # API routes
|
||||
│ │ ├── middleware/ # Auth, CORS
|
||||
│ │ └── services/ # Business logic
|
||||
│ ├── tests/
|
||||
│ ├── wrangler.toml
|
||||
│ ├── package.json
|
||||
│ └── tsconfig.json
|
||||
├── packages/ # Shared code
|
||||
│ └── types/
|
||||
│ ├── src/
|
||||
│ │ ├── api.ts # API types
|
||||
│ │ └── models.ts # Data models
|
||||
│ ├── package.json
|
||||
│ └── tsconfig.json
|
||||
├── docs/
|
||||
│ ├── README.md # Project overview
|
||||
│ ├── ARCHITECTURE.md # System architecture
|
||||
│ └── API.md # API documentation
|
||||
├── .github/
|
||||
│ └── workflows/
|
||||
│ └── deploy.yml # CI/CD pipeline
|
||||
├── package.json # Root workspace config
|
||||
├── pnpm-workspace.yaml # pnpm workspaces
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Features
|
||||
|
||||
### Frontend (React + TanStack)
|
||||
|
||||
**Tech Stack**:
|
||||
- **Vite**: Fast build tool
|
||||
- **TanStack Router**: Type-safe routing
|
||||
- **TanStack Query**: Server state management
|
||||
- **TanStack Table**: Data tables
|
||||
- **Zod**: Runtime validation
|
||||
|
||||
**File**: `frontend/src/main.tsx`
|
||||
```typescript
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { RouterProvider, createRouter } from '@tanstack/react-router';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { routeTree } from './routeTree.gen';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
const router = createRouter({ routeTree });
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RouterProvider router={router} />
|
||||
</QueryClientProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
```
|
||||
|
||||
**File**: `frontend/src/services/api.ts`
|
||||
```typescript
|
||||
import { apiClient } from '@my-app/types';
|
||||
|
||||
const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:8787';
|
||||
|
||||
export const api = {
|
||||
users: {
|
||||
list: () => fetch(`${API_BASE}/api/users`).then(r => r.json()),
|
||||
get: (id: string) => fetch(`${API_BASE}/api/users/${id}`).then(r => r.json()),
|
||||
create: (data: any) =>
|
||||
fetch(`${API_BASE}/api/users`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
}).then(r => r.json()),
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Backend (Cloudflare Worker)
|
||||
|
||||
**File**: `backend/src/index.ts`
|
||||
```typescript
|
||||
import { Hono } from 'hono';
|
||||
import { cors } from 'hono/cors';
|
||||
import { userRoutes } from './routes/users';
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.use('*', cors({ origin: process.env.FRONTEND_URL || '*' }));
|
||||
app.route('/api/users', userRoutes);
|
||||
|
||||
export default app;
|
||||
```
|
||||
|
||||
### Shared Types
|
||||
|
||||
**File**: `packages/types/src/models.ts`
|
||||
```typescript
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreateUserInput {
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface UpdateUserInput {
|
||||
email?: string;
|
||||
name?: string;
|
||||
}
|
||||
```
|
||||
|
||||
**File**: `packages/types/src/api.ts`
|
||||
```typescript
|
||||
import type { User } from './models';
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
data?: T;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface UserListResponse extends ApiResponse<User[]> {
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface UserResponse extends ApiResponse<User> {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workspace Configuration
|
||||
|
||||
### Root package.json (pnpm workspaces)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-fullstack-app",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "concurrently \"pnpm --filter frontend dev\" \"pnpm --filter backend dev\"",
|
||||
"build": "pnpm --filter \"./packages/*\" build && pnpm --filter frontend build && pnpm --filter backend build",
|
||||
"test": "pnpm --recursive test",
|
||||
"deploy": "pnpm --filter backend deploy && pnpm --filter frontend deploy"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^8.2.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### pnpm-workspace.yaml
|
||||
|
||||
```yaml
|
||||
packages:
|
||||
- 'frontend'
|
||||
- 'backend'
|
||||
- 'packages/*'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### 1. Setup
|
||||
|
||||
```bash
|
||||
# Clone and install
|
||||
git clone <repo>
|
||||
cd my-fullstack-app
|
||||
pnpm install
|
||||
|
||||
# Setup database
|
||||
cd backend
|
||||
wrangler d1 create my-app-db
|
||||
# Update wrangler.toml with database ID
|
||||
cd ..
|
||||
|
||||
# Create .env files
|
||||
cp frontend/.env.example frontend/.env
|
||||
cp backend/.env.example backend/.env
|
||||
```
|
||||
|
||||
### 2. Development
|
||||
|
||||
```bash
|
||||
# Start both frontend and backend
|
||||
pnpm dev
|
||||
|
||||
# Frontend: http://localhost:5173
|
||||
# Backend: http://localhost:8787
|
||||
```
|
||||
|
||||
### 3. Testing
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pnpm test
|
||||
|
||||
# Test specific workspace
|
||||
pnpm --filter frontend test
|
||||
pnpm --filter backend test
|
||||
```
|
||||
|
||||
### 4. Deployment
|
||||
|
||||
```bash
|
||||
# Deploy backend (Cloudflare Workers)
|
||||
cd backend
|
||||
pnpm deploy
|
||||
|
||||
# Deploy frontend (Cloudflare Pages)
|
||||
cd ../frontend
|
||||
pnpm build
|
||||
wrangler pages deploy dist
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CI/CD Pipeline
|
||||
|
||||
**File**: `.github/workflows/deploy.yml`
|
||||
|
||||
```yaml
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install
|
||||
- run: pnpm test
|
||||
- run: pnpm build
|
||||
|
||||
deploy-backend:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
- run: pnpm install
|
||||
- run: pnpm --filter backend deploy
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
deploy-frontend:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v2
|
||||
- run: pnpm install
|
||||
- run: pnpm --filter frontend build
|
||||
- uses: cloudflare/pages-action@v1
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
projectName: my-app
|
||||
directory: frontend/dist
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
User → React App → TanStack Query → API Client
|
||||
↓
|
||||
Cloudflare Worker
|
||||
↓
|
||||
D1 Database
|
||||
```
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
```typescript
|
||||
// frontend/src/lib/auth.ts
|
||||
export async function login(email: string, password: string) {
|
||||
const response = await fetch(`${API_BASE}/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
const { token } = await response.json();
|
||||
localStorage.setItem('token', token);
|
||||
return token;
|
||||
}
|
||||
|
||||
// backend/src/middleware/auth.ts
|
||||
export const auth = () => async (c, next) => {
|
||||
const token = c.req.header('Authorization')?.replace('Bearer ', '');
|
||||
if (!token) return c.json({ error: 'Unauthorized' }, 401);
|
||||
|
||||
// Verify JWT token
|
||||
const user = await verifyToken(token);
|
||||
c.set('user', user);
|
||||
await next();
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Metrics
|
||||
|
||||
| Component | Files | LOC | Tests | Coverage |
|
||||
|-----------|-------|-----|-------|----------|
|
||||
| Frontend | 15 | ~350 | 8 | 85% |
|
||||
| Backend | 12 | ~300 | 6 | 90% |
|
||||
| Shared | 4 | ~80 | 2 | 100% |
|
||||
| Docs | 4 | ~120 | - | - |
|
||||
| **Total** | **35** | **~850** | **16** | **88%** |
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ Run `pnpm install` to install dependencies
|
||||
2. ✅ Setup D1 database and update configuration
|
||||
3. ✅ Run `pnpm dev` to start development servers
|
||||
4. ✅ Implement your business logic
|
||||
5. ✅ Deploy with `pnpm deploy`
|
||||
|
||||
---
|
||||
|
||||
**Setup Time**: 30 minutes
|
||||
**Production Ready**: Yes
|
||||
**Deployment**: Cloudflare Pages + Workers
|
||||
**Monitoring**: Built-in observability
|
||||
Reference in New Issue
Block a user