create-featurebased-architecture-backend 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -0
- package/dist/index.js +828 -0
- package/package.json +42 -0
- package/templates/blank-hono/.env.example +4 -0
- package/templates/blank-hono/README.md +53 -0
- package/templates/blank-hono/package.json +18 -0
- package/templates/blank-hono/src/config/database.ts +4 -0
- package/templates/blank-hono/src/config/env.ts +5 -0
- package/templates/blank-hono/src/config/index.ts +2 -0
- package/templates/blank-hono/src/features/.gitkeep.ts +21 -0
- package/templates/blank-hono/src/index.ts +22 -0
- package/templates/blank-hono/src/routes/index.ts +8 -0
- package/templates/blank-hono/src/shared/index.ts +2 -0
- package/templates/blank-hono/src/shared/types/index.ts +16 -0
- package/templates/blank-hono/src/shared/utils/index.ts +1 -0
- package/templates/blank-hono/src/shared/utils/response.ts +10 -0
- package/templates/blank-hono/tsconfig.json +22 -0
- package/templates/blank-ts/README.md +30 -0
- package/templates/blank-ts/package.json +15 -0
- package/templates/blank-ts/src/features/.gitkeep.ts +16 -0
- package/templates/blank-ts/src/index.ts +7 -0
- package/templates/blank-ts/src/shared/utils/index.ts +3 -0
- package/templates/blank-ts/tsconfig.json +21 -0
- package/templates/react/README.md +34 -0
- package/templates/react/index.html +12 -0
- package/templates/react/package.json +22 -0
- package/templates/react/src/App.tsx +10 -0
- package/templates/react/src/features/home/components/HomePage.tsx +8 -0
- package/templates/react/src/features/home/index.ts +1 -0
- package/templates/react/src/index.css +10 -0
- package/templates/react/src/main.tsx +13 -0
- package/templates/react/src/shared/components/Button.tsx +29 -0
- package/templates/react/src/shared/components/index.ts +1 -0
- package/templates/react/tsconfig.json +27 -0
- package/templates/react/tsconfig.node.json +11 -0
- package/templates/react/vite.config.ts +12 -0
- package/templates/user-management-backend/.env.example +4 -0
- package/templates/user-management-backend/README.md +105 -0
- package/templates/user-management-backend/package.json +19 -0
- package/templates/user-management-backend/src/config/database.ts +4 -0
- package/templates/user-management-backend/src/config/env.ts +5 -0
- package/templates/user-management-backend/src/config/index.ts +2 -0
- package/templates/user-management-backend/src/features/users/controller.ts +100 -0
- package/templates/user-management-backend/src/features/users/index.ts +6 -0
- package/templates/user-management-backend/src/features/users/repository.ts +57 -0
- package/templates/user-management-backend/src/features/users/routes.ts +10 -0
- package/templates/user-management-backend/src/features/users/schema.ts +15 -0
- package/templates/user-management-backend/src/features/users/service.ts +40 -0
- package/templates/user-management-backend/src/features/users/types.ts +17 -0
- package/templates/user-management-backend/src/index.ts +22 -0
- package/templates/user-management-backend/src/routes/index.ts +8 -0
- package/templates/user-management-backend/src/shared/index.ts +2 -0
- package/templates/user-management-backend/src/shared/types/index.ts +16 -0
- package/templates/user-management-backend/src/shared/utils/index.ts +1 -0
- package/templates/user-management-backend/src/shared/utils/response.ts +10 -0
- package/templates/user-management-backend/tsconfig.json +22 -0
- package/templates/user-management-frontend/.env.example +1 -0
- package/templates/user-management-frontend/README.md +53 -0
- package/templates/user-management-frontend/index.html +12 -0
- package/templates/user-management-frontend/package.json +22 -0
- package/templates/user-management-frontend/src/App.tsx +19 -0
- package/templates/user-management-frontend/src/config/env.ts +1 -0
- package/templates/user-management-frontend/src/config/index.ts +1 -0
- package/templates/user-management-frontend/src/features/users/components/UserFormPage.tsx +76 -0
- package/templates/user-management-frontend/src/features/users/components/UsersPage.tsx +61 -0
- package/templates/user-management-frontend/src/features/users/components/index.ts +2 -0
- package/templates/user-management-frontend/src/features/users/hooks/index.ts +1 -0
- package/templates/user-management-frontend/src/features/users/hooks/useUsers.ts +45 -0
- package/templates/user-management-frontend/src/features/users/index.ts +4 -0
- package/templates/user-management-frontend/src/features/users/services/index.ts +1 -0
- package/templates/user-management-frontend/src/features/users/services/userService.ts +48 -0
- package/templates/user-management-frontend/src/features/users/types.ts +24 -0
- package/templates/user-management-frontend/src/index.css +108 -0
- package/templates/user-management-frontend/src/main.tsx +13 -0
- package/templates/user-management-frontend/tsconfig.json +27 -0
- package/templates/user-management-frontend/tsconfig.node.json +11 -0
- package/templates/user-management-frontend/vite.config.ts +12 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A User Management System backend with feature-based architecture using Bun, Hono, and NeonDB.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── config/ # Configuration (env, database)
|
|
10
|
+
├── features/ # Feature modules
|
|
11
|
+
│ └── users/
|
|
12
|
+
│ ├── routes.ts
|
|
13
|
+
│ ├── controller.ts
|
|
14
|
+
│ ├── service.ts
|
|
15
|
+
│ ├── repository.ts
|
|
16
|
+
│ ├── types.ts
|
|
17
|
+
│ └── schema.ts
|
|
18
|
+
├── routes/ # Route aggregation
|
|
19
|
+
├── shared/ # Shared utilities and types
|
|
20
|
+
│ ├── types/
|
|
21
|
+
│ └── utils/
|
|
22
|
+
└── index.ts # Application entry
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Getting Started
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Install dependencies
|
|
29
|
+
bun install
|
|
30
|
+
|
|
31
|
+
# Copy environment file
|
|
32
|
+
cp .env.example .env
|
|
33
|
+
|
|
34
|
+
# Update DATABASE_URL with your NeonDB connection string
|
|
35
|
+
|
|
36
|
+
# Run development server
|
|
37
|
+
bun run dev
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Environment Variables
|
|
41
|
+
|
|
42
|
+
| Variable | Description |
|
|
43
|
+
|----------|-------------|
|
|
44
|
+
| `DATABASE_URL` | NeonDB PostgreSQL connection string |
|
|
45
|
+
| `PORT` | Server port (default: 3000) |
|
|
46
|
+
| `NODE_ENV` | Environment (development/production) |
|
|
47
|
+
|
|
48
|
+
## Database Setup (NeonDB)
|
|
49
|
+
|
|
50
|
+
Run the following SQL in your NeonDB console to create the users table:
|
|
51
|
+
|
|
52
|
+
```sql
|
|
53
|
+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
54
|
+
|
|
55
|
+
CREATE TABLE users (
|
|
56
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
57
|
+
email VARCHAR(255) UNIQUE NOT NULL,
|
|
58
|
+
name VARCHAR(100) NOT NULL,
|
|
59
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
60
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
-- Optional: Add index for email lookups
|
|
64
|
+
CREATE INDEX idx_users_email ON users(email);
|
|
65
|
+
|
|
66
|
+
-- Optional: Add trigger to auto-update updated_at
|
|
67
|
+
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
68
|
+
RETURNS TRIGGER AS $$
|
|
69
|
+
BEGIN
|
|
70
|
+
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
71
|
+
RETURN NEW;
|
|
72
|
+
END;
|
|
73
|
+
$$ language 'plpgsql';
|
|
74
|
+
|
|
75
|
+
CREATE TRIGGER update_users_updated_at
|
|
76
|
+
BEFORE UPDATE ON users
|
|
77
|
+
FOR EACH ROW
|
|
78
|
+
EXECUTE FUNCTION update_updated_at_column();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## API Endpoints
|
|
82
|
+
|
|
83
|
+
| Method | Endpoint | Description |
|
|
84
|
+
|--------|----------|-------------|
|
|
85
|
+
| GET | `/api/users` | Get all users |
|
|
86
|
+
| GET | `/api/users/:id` | Get user by ID |
|
|
87
|
+
| POST | `/api/users` | Create new user |
|
|
88
|
+
| PUT | `/api/users/:id` | Update user |
|
|
89
|
+
| DELETE | `/api/users/:id` | Delete user |
|
|
90
|
+
|
|
91
|
+
### Request/Response Examples
|
|
92
|
+
|
|
93
|
+
**Create User**
|
|
94
|
+
```bash
|
|
95
|
+
curl -X POST http://localhost:3000/api/users \
|
|
96
|
+
-H "Content-Type: application/json" \
|
|
97
|
+
-d '{"email": "user@example.com", "name": "John Doe"}'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Update User**
|
|
101
|
+
```bash
|
|
102
|
+
curl -X PUT http://localhost:3000/api/users/{id} \
|
|
103
|
+
-H "Content-Type: application/json" \
|
|
104
|
+
-d '{"name": "Jane Doe"}'
|
|
105
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "bun run --watch src/index.ts",
|
|
7
|
+
"start": "bun run src/index.ts",
|
|
8
|
+
"build": "bun build src/index.ts --outdir ./dist --target bun"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"hono": "^4.11.3",
|
|
12
|
+
"@neondatabase/serverless": "^0.9.0",
|
|
13
|
+
"zod": "^3.22.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/bun": "latest",
|
|
17
|
+
"typescript": "^5.0.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import { userService } from "./service";
|
|
3
|
+
import { createUserSchema, updateUserSchema, userIdSchema } from "./schema";
|
|
4
|
+
import { successResponse, errorResponse } from "../../shared/utils/response";
|
|
5
|
+
|
|
6
|
+
export const userController = {
|
|
7
|
+
async getAll(c: Context) {
|
|
8
|
+
try {
|
|
9
|
+
const users = await userService.getAllUsers();
|
|
10
|
+
return successResponse(c, users);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
return errorResponse(c, "Failed to fetch users", 500);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
async getById(c: Context) {
|
|
17
|
+
try {
|
|
18
|
+
const { id } = c.req.param();
|
|
19
|
+
const validation = userIdSchema.safeParse({ id });
|
|
20
|
+
|
|
21
|
+
if (!validation.success) {
|
|
22
|
+
return errorResponse(c, validation.error.errors[0].message);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const user = await userService.getUserById(id);
|
|
26
|
+
if (!user) {
|
|
27
|
+
return errorResponse(c, "User not found", 404);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return successResponse(c, user);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return errorResponse(c, "Failed to fetch user", 500);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
async create(c: Context) {
|
|
37
|
+
try {
|
|
38
|
+
const body = await c.req.json();
|
|
39
|
+
const validation = createUserSchema.safeParse(body);
|
|
40
|
+
|
|
41
|
+
if (!validation.success) {
|
|
42
|
+
return errorResponse(c, validation.error.errors[0].message);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const user = await userService.createUser(validation.data);
|
|
46
|
+
return successResponse(c, user, "User created successfully", 201);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : "Failed to create user";
|
|
49
|
+
return errorResponse(c, message, error instanceof Error && error.message === "Email already exists" ? 409 : 500);
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async update(c: Context) {
|
|
54
|
+
try {
|
|
55
|
+
const { id } = c.req.param();
|
|
56
|
+
const idValidation = userIdSchema.safeParse({ id });
|
|
57
|
+
|
|
58
|
+
if (!idValidation.success) {
|
|
59
|
+
return errorResponse(c, idValidation.error.errors[0].message);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const body = await c.req.json();
|
|
63
|
+
const validation = updateUserSchema.safeParse(body);
|
|
64
|
+
|
|
65
|
+
if (!validation.success) {
|
|
66
|
+
return errorResponse(c, validation.error.errors[0].message);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const user = await userService.updateUser(id, validation.data);
|
|
70
|
+
if (!user) {
|
|
71
|
+
return errorResponse(c, "User not found", 404);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return successResponse(c, user, "User updated successfully");
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const message = error instanceof Error ? error.message : "Failed to update user";
|
|
77
|
+
return errorResponse(c, message, error instanceof Error && error.message === "Email already exists" ? 409 : 500);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async delete(c: Context) {
|
|
82
|
+
try {
|
|
83
|
+
const { id } = c.req.param();
|
|
84
|
+
const validation = userIdSchema.safeParse({ id });
|
|
85
|
+
|
|
86
|
+
if (!validation.success) {
|
|
87
|
+
return errorResponse(c, validation.error.errors[0].message);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const deleted = await userService.deleteUser(id);
|
|
91
|
+
if (!deleted) {
|
|
92
|
+
return errorResponse(c, "User not found", 404);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return successResponse(c, null, "User deleted successfully");
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return errorResponse(c, "Failed to delete user", 500);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { sql } from "../../config/database";
|
|
2
|
+
import type { User, CreateUserDto, UpdateUserDto } from "./types";
|
|
3
|
+
|
|
4
|
+
export const userRepository = {
|
|
5
|
+
async findAll(): Promise<User[]> {
|
|
6
|
+
const result = await sql`SELECT * FROM users ORDER BY created_at DESC`;
|
|
7
|
+
return result as User[];
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
async findById(id: string): Promise<User | null> {
|
|
11
|
+
const result = await sql`SELECT * FROM users WHERE id = ${id}`;
|
|
12
|
+
return (result[0] as User) || null;
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
16
|
+
const result = await sql`SELECT * FROM users WHERE email = ${email}`;
|
|
17
|
+
return (result[0] as User) || null;
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
async create(data: CreateUserDto): Promise<User> {
|
|
21
|
+
const result = await sql`
|
|
22
|
+
INSERT INTO users (email, name)
|
|
23
|
+
VALUES (${data.email}, ${data.name})
|
|
24
|
+
RETURNING *
|
|
25
|
+
`;
|
|
26
|
+
return result[0] as User;
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
async update(id: string, data: UpdateUserDto): Promise<User | null> {
|
|
30
|
+
const fields: string[] = [];
|
|
31
|
+
const values: unknown[] = [];
|
|
32
|
+
|
|
33
|
+
if (data.email) {
|
|
34
|
+
fields.push("email");
|
|
35
|
+
values.push(data.email);
|
|
36
|
+
}
|
|
37
|
+
if (data.name) {
|
|
38
|
+
fields.push("name");
|
|
39
|
+
values.push(data.name);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (fields.length === 0) return this.findById(id);
|
|
43
|
+
|
|
44
|
+
const result = await sql`
|
|
45
|
+
UPDATE users
|
|
46
|
+
SET ${sql(Object.fromEntries(fields.map((f, i) => [f, values[i]])))}
|
|
47
|
+
WHERE id = ${id}
|
|
48
|
+
RETURNING *
|
|
49
|
+
`;
|
|
50
|
+
return (result[0] as User) || null;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async delete(id: string): Promise<boolean> {
|
|
54
|
+
const result = await sql`DELETE FROM users WHERE id = ${id} RETURNING id`;
|
|
55
|
+
return result.length > 0;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { userController } from "./controller";
|
|
3
|
+
|
|
4
|
+
export const userRoutes = new Hono();
|
|
5
|
+
|
|
6
|
+
userRoutes.get("/", userController.getAll);
|
|
7
|
+
userRoutes.get("/:id", userController.getById);
|
|
8
|
+
userRoutes.post("/", userController.create);
|
|
9
|
+
userRoutes.put("/:id", userController.update);
|
|
10
|
+
userRoutes.delete("/:id", userController.delete);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const createUserSchema = z.object({
|
|
4
|
+
email: z.string().email("Invalid email format"),
|
|
5
|
+
name: z.string().min(1, "Name is required").max(100, "Name too long"),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const updateUserSchema = z.object({
|
|
9
|
+
email: z.string().email("Invalid email format").optional(),
|
|
10
|
+
name: z.string().min(1).max(100).optional(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const userIdSchema = z.object({
|
|
14
|
+
id: z.string().uuid("Invalid user ID"),
|
|
15
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { userRepository } from "./repository";
|
|
2
|
+
import type { CreateUserDto, UpdateUserDto, User } from "./types";
|
|
3
|
+
|
|
4
|
+
export const userService = {
|
|
5
|
+
async getAllUsers(): Promise<User[]> {
|
|
6
|
+
return userRepository.findAll();
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
async getUserById(id: string): Promise<User | null> {
|
|
10
|
+
return userRepository.findById(id);
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
async createUser(data: CreateUserDto): Promise<User> {
|
|
14
|
+
const existing = await userRepository.findByEmail(data.email);
|
|
15
|
+
if (existing) {
|
|
16
|
+
throw new Error("Email already exists");
|
|
17
|
+
}
|
|
18
|
+
return userRepository.create(data);
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
async updateUser(id: string, data: UpdateUserDto): Promise<User | null> {
|
|
22
|
+
const user = await userRepository.findById(id);
|
|
23
|
+
if (!user) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (data.email && data.email !== user.email) {
|
|
28
|
+
const existing = await userRepository.findByEmail(data.email);
|
|
29
|
+
if (existing) {
|
|
30
|
+
throw new Error("Email already exists");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return userRepository.update(id, data);
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
async deleteUser(id: string): Promise<boolean> {
|
|
38
|
+
return userRepository.delete(id);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface User {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name: string;
|
|
5
|
+
created_at: Date;
|
|
6
|
+
updated_at: Date;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CreateUserDto {
|
|
10
|
+
email: string;
|
|
11
|
+
name: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface UpdateUserDto {
|
|
15
|
+
email?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { cors } from "hono/cors";
|
|
3
|
+
import { logger } from "hono/logger";
|
|
4
|
+
import { env } from "./config/env";
|
|
5
|
+
import { appRouter } from "./routes";
|
|
6
|
+
|
|
7
|
+
const app = new Hono();
|
|
8
|
+
|
|
9
|
+
// Middleware
|
|
10
|
+
app.use("*", logger());
|
|
11
|
+
app.use("*", cors());
|
|
12
|
+
|
|
13
|
+
// Routes
|
|
14
|
+
app.route("/api", appRouter);
|
|
15
|
+
|
|
16
|
+
// Health check
|
|
17
|
+
app.get("/health", (c) => c.json({ status: "ok", timestamp: new Date().toISOString() }));
|
|
18
|
+
|
|
19
|
+
export default {
|
|
20
|
+
port: env.PORT,
|
|
21
|
+
fetch: app.fetch,
|
|
22
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Shared types across features
|
|
2
|
+
export interface ApiResponse<T> {
|
|
3
|
+
success: boolean;
|
|
4
|
+
data?: T;
|
|
5
|
+
error?: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
|
|
10
|
+
pagination: {
|
|
11
|
+
page: number;
|
|
12
|
+
limit: number;
|
|
13
|
+
total: number;
|
|
14
|
+
totalPages: number;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./response";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import type { ApiResponse } from "../types";
|
|
3
|
+
|
|
4
|
+
export function successResponse<T>(c: Context, data: T, message?: string, status = 200) {
|
|
5
|
+
return c.json<ApiResponse<T>>({ success: true, data, message }, status);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function errorResponse(c: Context, error: string, status = 400) {
|
|
9
|
+
return c.json<ApiResponse<never>>({ success: false, error }, status);
|
|
10
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "./src",
|
|
12
|
+
"baseUrl": ".",
|
|
13
|
+
"paths": {
|
|
14
|
+
"@/*": ["src/*"],
|
|
15
|
+
"@/features/*": ["src/features/*"],
|
|
16
|
+
"@/shared/*": ["src/shared/*"],
|
|
17
|
+
"@/config/*": ["src/config/*"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "dist"]
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VITE_API_URL=http://localhost:3000/api
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A User Management System frontend with feature-based architecture using React, Vite, and Bun.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── config/ # Configuration
|
|
10
|
+
├── features/ # Feature modules
|
|
11
|
+
│ └── users/
|
|
12
|
+
│ ├── components/
|
|
13
|
+
│ ├── hooks/
|
|
14
|
+
│ ├── services/
|
|
15
|
+
│ ├── types.ts
|
|
16
|
+
│ └── index.ts
|
|
17
|
+
├── shared/ # Shared components
|
|
18
|
+
├── App.tsx
|
|
19
|
+
├── main.tsx
|
|
20
|
+
└── index.css
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Getting Started
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Install dependencies
|
|
27
|
+
bun install
|
|
28
|
+
|
|
29
|
+
# Copy environment file
|
|
30
|
+
cp .env.example .env
|
|
31
|
+
|
|
32
|
+
# Update VITE_API_URL if your backend runs on a different port
|
|
33
|
+
|
|
34
|
+
# Run development server
|
|
35
|
+
bun run dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Environment Variables
|
|
39
|
+
|
|
40
|
+
| Variable | Description |
|
|
41
|
+
|----------|-------------|
|
|
42
|
+
| `VITE_API_URL` | Backend API URL (default: http://localhost:3000/api) |
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- List all users
|
|
47
|
+
- Create new user
|
|
48
|
+
- Edit existing user
|
|
49
|
+
- Delete user
|
|
50
|
+
|
|
51
|
+
## Backend Setup
|
|
52
|
+
|
|
53
|
+
This frontend requires the User Management Backend to be running. See the backend template for setup instructions.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{PROJECT_NAME}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "bunx --bun vite",
|
|
7
|
+
"build": "bunx --bun vite build",
|
|
8
|
+
"preview": "bunx --bun vite preview"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"react": "^18.2.0",
|
|
12
|
+
"react-dom": "^18.2.0",
|
|
13
|
+
"react-router-dom": "^6.20.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/react": "^18.2.0",
|
|
17
|
+
"@types/react-dom": "^18.2.0",
|
|
18
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
19
|
+
"typescript": "^5.0.0",
|
|
20
|
+
"vite": "^5.0.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Routes, Route } from "react-router-dom";
|
|
2
|
+
import { UsersPage, UserFormPage } from "./features/users";
|
|
3
|
+
|
|
4
|
+
export function App() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="app">
|
|
7
|
+
<header className="header">
|
|
8
|
+
<h1>User Management</h1>
|
|
9
|
+
</header>
|
|
10
|
+
<main className="main">
|
|
11
|
+
<Routes>
|
|
12
|
+
<Route path="/" element={<UsersPage />} />
|
|
13
|
+
<Route path="/users/new" element={<UserFormPage />} />
|
|
14
|
+
<Route path="/users/:id/edit" element={<UserFormPage />} />
|
|
15
|
+
</Routes>
|
|
16
|
+
</main>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const API_URL = import.meta.env.VITE_API_URL || "http://localhost:3000/api";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./env";
|