Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:24:43 +08:00
commit 0961c5806a
21 changed files with 3552 additions and 0 deletions

View File

@@ -0,0 +1,367 @@
/**
* Complete Cloudflare Worker with Drizzle ORM and D1
*
* This template demonstrates a full-featured Worker using:
* - Hono for routing
* - Drizzle ORM for database queries
* - D1 for serverless SQLite
* - TypeScript for type safety
*/
import { Hono } from 'hono';
import { drizzle } from 'drizzle-orm/d1';
import { cors } from 'hono/cors';
import { prettyJSON } from 'hono/pretty-json';
import { eq } from 'drizzle-orm';
import * as schema from './db/schema';
import { users, posts, comments } from './db/schema';
/**
* Environment Interface
*
* Define all Cloudflare bindings
*/
export interface Env {
DB: D1Database; // D1 database binding
// Add other bindings as needed:
// KV: KVNamespace;
// R2: R2Bucket;
// AI: Ai;
}
/**
* Initialize Hono App
*/
const app = new Hono<{ Bindings: Env }>();
/**
* Middleware
*/
// CORS
app.use('/*', cors());
// Pretty JSON responses
app.use('/*', prettyJSON());
// Add database to context
app.use('*', async (c, next) => {
// Initialize Drizzle client with schema for relational queries
c.set('db', drizzle(c.env.DB, { schema }));
await next();
});
/**
* Health Check
*/
app.get('/', (c) => {
return c.json({
message: 'Cloudflare Worker with Drizzle ORM + D1',
status: 'ok',
timestamp: new Date().toISOString(),
});
});
/**
* Users Routes
*/
// Get all users
app.get('/api/users', async (c) => {
const db = c.get('db');
const allUsers = await db.select().from(users).all();
return c.json(allUsers);
});
// Get user by ID
app.get('/api/users/:id', async (c) => {
const db = c.get('db');
const id = parseInt(c.req.param('id'));
if (isNaN(id)) {
return c.json({ error: 'Invalid user ID' }, 400);
}
const user = await db.select().from(users).where(eq(users.id, id)).get();
if (!user) {
return c.json({ error: 'User not found' }, 404);
}
return c.json(user);
});
// Get user with posts (relational query)
app.get('/api/users/:id/posts', async (c) => {
const db = c.get('db');
const id = parseInt(c.req.param('id'));
if (isNaN(id)) {
return c.json({ error: 'Invalid user ID' }, 400);
}
const userWithPosts = await db.query.users.findFirst({
where: eq(users.id, id),
with: {
posts: {
orderBy: (posts, { desc }) => [desc(posts.createdAt)],
},
},
});
if (!userWithPosts) {
return c.json({ error: 'User not found' }, 404);
}
return c.json(userWithPosts);
});
// Create user
app.post('/api/users', async (c) => {
const db = c.get('db');
try {
const body = await c.req.json();
// Validate input
if (!body.email || !body.name) {
return c.json({ error: 'Email and name are required' }, 400);
}
// Check if user exists
const existing = await db
.select()
.from(users)
.where(eq(users.email, body.email))
.get();
if (existing) {
return c.json({ error: 'User with this email already exists' }, 409);
}
// Create user
const [newUser] = await db
.insert(users)
.values({
email: body.email,
name: body.name,
bio: body.bio,
})
.returning();
return c.json(newUser, 201);
} catch (error) {
console.error('Error creating user:', error);
return c.json({ error: 'Failed to create user' }, 500);
}
});
// Update user
app.put('/api/users/:id', async (c) => {
const db = c.get('db');
const id = parseInt(c.req.param('id'));
if (isNaN(id)) {
return c.json({ error: 'Invalid user ID' }, 400);
}
try {
const body = await c.req.json();
const [updated] = await db
.update(users)
.set({
name: body.name,
bio: body.bio,
updatedAt: new Date(),
})
.where(eq(users.id, id))
.returning();
if (!updated) {
return c.json({ error: 'User not found' }, 404);
}
return c.json(updated);
} catch (error) {
console.error('Error updating user:', error);
return c.json({ error: 'Failed to update user' }, 500);
}
});
// Delete user
app.delete('/api/users/:id', async (c) => {
const db = c.get('db');
const id = parseInt(c.req.param('id'));
if (isNaN(id)) {
return c.json({ error: 'Invalid user ID' }, 400);
}
try {
const [deleted] = await db
.delete(users)
.where(eq(users.id, id))
.returning();
if (!deleted) {
return c.json({ error: 'User not found' }, 404);
}
return c.json({ message: 'User deleted successfully', user: deleted });
} catch (error) {
console.error('Error deleting user:', error);
return c.json({ error: 'Failed to delete user' }, 500);
}
});
/**
* Posts Routes
*/
// Get all published posts
app.get('/api/posts', async (c) => {
const db = c.get('db');
const publishedPosts = await db.query.posts.findMany({
where: eq(posts.published, true),
with: {
author: {
columns: {
id: true,
name: true,
email: true,
},
},
},
orderBy: (posts, { desc }) => [desc(posts.createdAt)],
});
return c.json(publishedPosts);
});
// Get post by ID (with author and comments)
app.get('/api/posts/:id', async (c) => {
const db = c.get('db');
const id = parseInt(c.req.param('id'));
if (isNaN(id)) {
return c.json({ error: 'Invalid post ID' }, 400);
}
const post = await db.query.posts.findFirst({
where: eq(posts.id, id),
with: {
author: true,
comments: {
with: {
author: true,
},
orderBy: (comments, { desc }) => [desc(comments.createdAt)],
},
},
});
if (!post) {
return c.json({ error: 'Post not found' }, 404);
}
return c.json(post);
});
// Create post
app.post('/api/posts', async (c) => {
const db = c.get('db');
try {
const body = await c.req.json();
// Validate input
if (!body.title || !body.slug || !body.content || !body.authorId) {
return c.json(
{ error: 'Title, slug, content, and authorId are required' },
400
);
}
// Check if author exists
const author = await db
.select()
.from(users)
.where(eq(users.id, body.authorId))
.get();
if (!author) {
return c.json({ error: 'Author not found' }, 404);
}
// Create post
const [newPost] = await db
.insert(posts)
.values({
title: body.title,
slug: body.slug,
content: body.content,
authorId: body.authorId,
published: body.published ?? false,
})
.returning();
return c.json(newPost, 201);
} catch (error) {
console.error('Error creating post:', error);
return c.json({ error: 'Failed to create post' }, 500);
}
});
/**
* Error Handling
*/
app.onError((err, c) => {
console.error('Unhandled error:', err);
return c.json({ error: 'Internal server error' }, 500);
});
/**
* 404 Handler
*/
app.notFound((c) => {
return c.json({ error: 'Not found' }, 404);
});
/**
* Export Worker
*/
export default app;
/**
* Usage:
*
* 1. Deploy: npx wrangler deploy
* 2. Test: curl https://your-worker.workers.dev/
*
* API Endpoints:
* - GET /api/users - Get all users
* - GET /api/users/:id - Get user by ID
* - GET /api/users/:id/posts - Get user with posts
* - POST /api/users - Create user
* - PUT /api/users/:id - Update user
* - DELETE /api/users/:id - Delete user
* - GET /api/posts - Get all published posts
* - GET /api/posts/:id - Get post with author and comments
* - POST /api/posts - Create post
*/
/**
* Type-Safe Context
*
* For better TypeScript support, you can extend Hono's context
*/
declare module 'hono' {
interface ContextVariableMap {
db: ReturnType<typeof drizzle>;
}
}