create-featurebased-architecture 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.
Files changed (77) hide show
  1. package/README.md +89 -0
  2. package/dist/index.js +828 -0
  3. package/package.json +42 -0
  4. package/templates/blank-hono/.env.example +4 -0
  5. package/templates/blank-hono/README.md +53 -0
  6. package/templates/blank-hono/package.json +18 -0
  7. package/templates/blank-hono/src/config/database.ts +4 -0
  8. package/templates/blank-hono/src/config/env.ts +5 -0
  9. package/templates/blank-hono/src/config/index.ts +2 -0
  10. package/templates/blank-hono/src/features/.gitkeep.ts +21 -0
  11. package/templates/blank-hono/src/index.ts +22 -0
  12. package/templates/blank-hono/src/routes/index.ts +8 -0
  13. package/templates/blank-hono/src/shared/index.ts +2 -0
  14. package/templates/blank-hono/src/shared/types/index.ts +16 -0
  15. package/templates/blank-hono/src/shared/utils/index.ts +1 -0
  16. package/templates/blank-hono/src/shared/utils/response.ts +10 -0
  17. package/templates/blank-hono/tsconfig.json +22 -0
  18. package/templates/blank-ts/README.md +30 -0
  19. package/templates/blank-ts/package.json +15 -0
  20. package/templates/blank-ts/src/features/.gitkeep.ts +16 -0
  21. package/templates/blank-ts/src/index.ts +7 -0
  22. package/templates/blank-ts/src/shared/utils/index.ts +3 -0
  23. package/templates/blank-ts/tsconfig.json +21 -0
  24. package/templates/react/README.md +34 -0
  25. package/templates/react/index.html +12 -0
  26. package/templates/react/package.json +22 -0
  27. package/templates/react/src/App.tsx +10 -0
  28. package/templates/react/src/features/home/components/HomePage.tsx +8 -0
  29. package/templates/react/src/features/home/index.ts +1 -0
  30. package/templates/react/src/index.css +10 -0
  31. package/templates/react/src/main.tsx +13 -0
  32. package/templates/react/src/shared/components/Button.tsx +29 -0
  33. package/templates/react/src/shared/components/index.ts +1 -0
  34. package/templates/react/tsconfig.json +27 -0
  35. package/templates/react/tsconfig.node.json +11 -0
  36. package/templates/react/vite.config.ts +12 -0
  37. package/templates/user-management-backend/.env.example +4 -0
  38. package/templates/user-management-backend/README.md +105 -0
  39. package/templates/user-management-backend/package.json +19 -0
  40. package/templates/user-management-backend/src/config/database.ts +4 -0
  41. package/templates/user-management-backend/src/config/env.ts +5 -0
  42. package/templates/user-management-backend/src/config/index.ts +2 -0
  43. package/templates/user-management-backend/src/features/users/controller.ts +100 -0
  44. package/templates/user-management-backend/src/features/users/index.ts +6 -0
  45. package/templates/user-management-backend/src/features/users/repository.ts +57 -0
  46. package/templates/user-management-backend/src/features/users/routes.ts +10 -0
  47. package/templates/user-management-backend/src/features/users/schema.ts +15 -0
  48. package/templates/user-management-backend/src/features/users/service.ts +40 -0
  49. package/templates/user-management-backend/src/features/users/types.ts +17 -0
  50. package/templates/user-management-backend/src/index.ts +22 -0
  51. package/templates/user-management-backend/src/routes/index.ts +8 -0
  52. package/templates/user-management-backend/src/shared/index.ts +2 -0
  53. package/templates/user-management-backend/src/shared/types/index.ts +16 -0
  54. package/templates/user-management-backend/src/shared/utils/index.ts +1 -0
  55. package/templates/user-management-backend/src/shared/utils/response.ts +10 -0
  56. package/templates/user-management-backend/tsconfig.json +22 -0
  57. package/templates/user-management-frontend/.env.example +1 -0
  58. package/templates/user-management-frontend/README.md +53 -0
  59. package/templates/user-management-frontend/index.html +12 -0
  60. package/templates/user-management-frontend/package.json +22 -0
  61. package/templates/user-management-frontend/src/App.tsx +19 -0
  62. package/templates/user-management-frontend/src/config/env.ts +1 -0
  63. package/templates/user-management-frontend/src/config/index.ts +1 -0
  64. package/templates/user-management-frontend/src/features/users/components/UserFormPage.tsx +76 -0
  65. package/templates/user-management-frontend/src/features/users/components/UsersPage.tsx +61 -0
  66. package/templates/user-management-frontend/src/features/users/components/index.ts +2 -0
  67. package/templates/user-management-frontend/src/features/users/hooks/index.ts +1 -0
  68. package/templates/user-management-frontend/src/features/users/hooks/useUsers.ts +45 -0
  69. package/templates/user-management-frontend/src/features/users/index.ts +4 -0
  70. package/templates/user-management-frontend/src/features/users/services/index.ts +1 -0
  71. package/templates/user-management-frontend/src/features/users/services/userService.ts +48 -0
  72. package/templates/user-management-frontend/src/features/users/types.ts +24 -0
  73. package/templates/user-management-frontend/src/index.css +108 -0
  74. package/templates/user-management-frontend/src/main.tsx +13 -0
  75. package/templates/user-management-frontend/tsconfig.json +27 -0
  76. package/templates/user-management-frontend/tsconfig.node.json +11 -0
  77. package/templates/user-management-frontend/vite.config.ts +12 -0
@@ -0,0 +1,76 @@
1
+ import { useState, useEffect } from "react";
2
+ import { useNavigate, useParams, Link } from "react-router-dom";
3
+ import { userService } from "../services/userService";
4
+ import type { CreateUserDto } from "../types";
5
+
6
+ export function UserFormPage() {
7
+ const { id } = useParams();
8
+ const navigate = useNavigate();
9
+ const isEditing = Boolean(id);
10
+
11
+ const [form, setForm] = useState<CreateUserDto>({ email: "", name: "" });
12
+ const [loading, setLoading] = useState(false);
13
+ const [error, setError] = useState<string | null>(null);
14
+
15
+ useEffect(() => {
16
+ if (id) {
17
+ userService.getById(id).then(setForm).catch((err) => setError(err.message));
18
+ }
19
+ }, [id]);
20
+
21
+ const handleSubmit = async (e: React.FormEvent) => {
22
+ e.preventDefault();
23
+ setLoading(true);
24
+ setError(null);
25
+
26
+ try {
27
+ if (isEditing && id) {
28
+ await userService.update(id, form);
29
+ } else {
30
+ await userService.create(form);
31
+ }
32
+ navigate("/");
33
+ } catch (err) {
34
+ setError(err instanceof Error ? err.message : "An error occurred");
35
+ } finally {
36
+ setLoading(false);
37
+ }
38
+ };
39
+
40
+ return (
41
+ <div className="card" style={{ maxWidth: "500px" }}>
42
+ <h2>{isEditing ? "Edit User" : "Create User"}</h2>
43
+
44
+ {error && <p className="error">{error}</p>}
45
+
46
+ <form onSubmit={handleSubmit}>
47
+ <div className="form-group">
48
+ <label>Name</label>
49
+ <input
50
+ type="text"
51
+ value={form.name}
52
+ onChange={(e) => setForm({ ...form, name: e.target.value })}
53
+ required
54
+ />
55
+ </div>
56
+
57
+ <div className="form-group">
58
+ <label>Email</label>
59
+ <input
60
+ type="email"
61
+ value={form.email}
62
+ onChange={(e) => setForm({ ...form, email: e.target.value })}
63
+ required
64
+ />
65
+ </div>
66
+
67
+ <div className="actions">
68
+ <button type="submit" className="btn btn-primary" disabled={loading}>
69
+ {loading ? "Saving..." : isEditing ? "Update" : "Create"}
70
+ </button>
71
+ <Link to="/" className="btn btn-secondary">Cancel</Link>
72
+ </div>
73
+ </form>
74
+ </div>
75
+ );
76
+ }
@@ -0,0 +1,61 @@
1
+ import { useNavigate, Link } from "react-router-dom";
2
+ import { useUsers } from "../hooks/useUsers";
3
+
4
+ export function UsersPage() {
5
+ const { users, loading, error, deleteUser } = useUsers();
6
+ const navigate = useNavigate();
7
+
8
+ const handleDelete = async (id: string) => {
9
+ if (confirm("Are you sure you want to delete this user?")) {
10
+ try {
11
+ await deleteUser(id);
12
+ } catch (err) {
13
+ alert(err instanceof Error ? err.message : "Failed to delete user");
14
+ }
15
+ }
16
+ };
17
+
18
+ if (loading) return <div className="loading">Loading...</div>;
19
+ if (error) return <div className="error">{error}</div>;
20
+
21
+ return (
22
+ <div className="card">
23
+ <div style={{ display: "flex", justifyContent: "space-between", marginBottom: "1rem" }}>
24
+ <h2>Users</h2>
25
+ <Link to="/users/new" className="btn btn-primary">Add User</Link>
26
+ </div>
27
+
28
+ {users.length === 0 ? (
29
+ <p>No users found. Create one to get started.</p>
30
+ ) : (
31
+ <table className="table">
32
+ <thead>
33
+ <tr>
34
+ <th>Name</th>
35
+ <th>Email</th>
36
+ <th>Created</th>
37
+ <th>Actions</th>
38
+ </tr>
39
+ </thead>
40
+ <tbody>
41
+ {users.map((user) => (
42
+ <tr key={user.id}>
43
+ <td>{user.name}</td>
44
+ <td>{user.email}</td>
45
+ <td>{new Date(user.created_at).toLocaleDateString()}</td>
46
+ <td className="actions">
47
+ <button className="btn btn-secondary" onClick={() => navigate(`/users/${user.id}/edit`)}>
48
+ Edit
49
+ </button>
50
+ <button className="btn btn-danger" onClick={() => handleDelete(user.id)}>
51
+ Delete
52
+ </button>
53
+ </td>
54
+ </tr>
55
+ ))}
56
+ </tbody>
57
+ </table>
58
+ )}
59
+ </div>
60
+ );
61
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./UsersPage";
2
+ export * from "./UserFormPage";
@@ -0,0 +1 @@
1
+ export * from "./useUsers";
@@ -0,0 +1,45 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import { userService } from "../services/userService";
3
+ import type { User, CreateUserDto, UpdateUserDto } from "../types";
4
+
5
+ export function useUsers() {
6
+ const [users, setUsers] = useState<User[]>([]);
7
+ const [loading, setLoading] = useState(true);
8
+ const [error, setError] = useState<string | null>(null);
9
+
10
+ const fetchUsers = useCallback(async () => {
11
+ try {
12
+ setLoading(true);
13
+ const data = await userService.getAll();
14
+ setUsers(data);
15
+ setError(null);
16
+ } catch (err) {
17
+ setError(err instanceof Error ? err.message : "Failed to fetch users");
18
+ } finally {
19
+ setLoading(false);
20
+ }
21
+ }, []);
22
+
23
+ const createUser = async (data: CreateUserDto) => {
24
+ const user = await userService.create(data);
25
+ setUsers((prev) => [user, ...prev]);
26
+ return user;
27
+ };
28
+
29
+ const updateUser = async (id: string, data: UpdateUserDto) => {
30
+ const user = await userService.update(id, data);
31
+ setUsers((prev) => prev.map((u) => (u.id === id ? user : u)));
32
+ return user;
33
+ };
34
+
35
+ const deleteUser = async (id: string) => {
36
+ await userService.delete(id);
37
+ setUsers((prev) => prev.filter((u) => u.id !== id));
38
+ };
39
+
40
+ useEffect(() => {
41
+ fetchUsers();
42
+ }, [fetchUsers]);
43
+
44
+ return { users, loading, error, fetchUsers, createUser, updateUser, deleteUser };
45
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./components";
2
+ export * from "./hooks";
3
+ export * from "./services";
4
+ export * from "./types";
@@ -0,0 +1 @@
1
+ export * from "./userService";
@@ -0,0 +1,48 @@
1
+ import { API_URL } from "../../config/env";
2
+ import type { User, CreateUserDto, UpdateUserDto, ApiResponse } from "./types";
3
+
4
+ const BASE_URL = `${API_URL}/users`;
5
+
6
+ export const userService = {
7
+ async getAll(): Promise<User[]> {
8
+ const res = await fetch(BASE_URL);
9
+ const data: ApiResponse<User[]> = await res.json();
10
+ if (!data.success) throw new Error(data.error);
11
+ return data.data || [];
12
+ },
13
+
14
+ async getById(id: string): Promise<User> {
15
+ const res = await fetch(`${BASE_URL}/${id}`);
16
+ const data: ApiResponse<User> = await res.json();
17
+ if (!data.success) throw new Error(data.error);
18
+ return data.data!;
19
+ },
20
+
21
+ async create(user: CreateUserDto): Promise<User> {
22
+ const res = await fetch(BASE_URL, {
23
+ method: "POST",
24
+ headers: { "Content-Type": "application/json" },
25
+ body: JSON.stringify(user),
26
+ });
27
+ const data: ApiResponse<User> = await res.json();
28
+ if (!data.success) throw new Error(data.error);
29
+ return data.data!;
30
+ },
31
+
32
+ async update(id: string, user: UpdateUserDto): Promise<User> {
33
+ const res = await fetch(`${BASE_URL}/${id}`, {
34
+ method: "PUT",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify(user),
37
+ });
38
+ const data: ApiResponse<User> = await res.json();
39
+ if (!data.success) throw new Error(data.error);
40
+ return data.data!;
41
+ },
42
+
43
+ async delete(id: string): Promise<void> {
44
+ const res = await fetch(`${BASE_URL}/${id}`, { method: "DELETE" });
45
+ const data: ApiResponse<null> = await res.json();
46
+ if (!data.success) throw new Error(data.error);
47
+ },
48
+ };
@@ -0,0 +1,24 @@
1
+ export interface User {
2
+ id: string;
3
+ email: string;
4
+ name: string;
5
+ created_at: string;
6
+ updated_at: string;
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
+ }
18
+
19
+ export interface ApiResponse<T> {
20
+ success: boolean;
21
+ data?: T;
22
+ error?: string;
23
+ message?: string;
24
+ }
@@ -0,0 +1,108 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: system-ui, -apple-system, sans-serif;
9
+ line-height: 1.5;
10
+ background: #f5f5f5;
11
+ }
12
+
13
+ .app {
14
+ min-height: 100vh;
15
+ }
16
+
17
+ .header {
18
+ background: #0070f3;
19
+ color: white;
20
+ padding: 1rem 2rem;
21
+ }
22
+
23
+ .main {
24
+ max-width: 1200px;
25
+ margin: 0 auto;
26
+ padding: 2rem;
27
+ }
28
+
29
+ .btn {
30
+ padding: 0.5rem 1rem;
31
+ border: none;
32
+ border-radius: 4px;
33
+ cursor: pointer;
34
+ font-size: 0.875rem;
35
+ }
36
+
37
+ .btn-primary {
38
+ background: #0070f3;
39
+ color: white;
40
+ }
41
+
42
+ .btn-danger {
43
+ background: #dc3545;
44
+ color: white;
45
+ }
46
+
47
+ .btn-secondary {
48
+ background: #6c757d;
49
+ color: white;
50
+ }
51
+
52
+ .form-group {
53
+ margin-bottom: 1rem;
54
+ }
55
+
56
+ .form-group label {
57
+ display: block;
58
+ margin-bottom: 0.25rem;
59
+ font-weight: 500;
60
+ }
61
+
62
+ .form-group input {
63
+ width: 100%;
64
+ padding: 0.5rem;
65
+ border: 1px solid #ddd;
66
+ border-radius: 4px;
67
+ font-size: 1rem;
68
+ }
69
+
70
+ .card {
71
+ background: white;
72
+ border-radius: 8px;
73
+ padding: 1.5rem;
74
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
75
+ }
76
+
77
+ .table {
78
+ width: 100%;
79
+ border-collapse: collapse;
80
+ }
81
+
82
+ .table th,
83
+ .table td {
84
+ padding: 0.75rem;
85
+ text-align: left;
86
+ border-bottom: 1px solid #eee;
87
+ }
88
+
89
+ .table th {
90
+ font-weight: 600;
91
+ background: #f9f9f9;
92
+ }
93
+
94
+ .actions {
95
+ display: flex;
96
+ gap: 0.5rem;
97
+ }
98
+
99
+ .error {
100
+ color: #dc3545;
101
+ font-size: 0.875rem;
102
+ }
103
+
104
+ .loading {
105
+ text-align: center;
106
+ padding: 2rem;
107
+ color: #666;
108
+ }
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import { BrowserRouter } from "react-router-dom";
4
+ import { App } from "./App";
5
+ import "./index.css";
6
+
7
+ ReactDOM.createRoot(document.getElementById("root")!).render(
8
+ <React.StrictMode>
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
12
+ </React.StrictMode>
13
+ );
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "baseUrl": ".",
19
+ "paths": {
20
+ "@/*": ["src/*"],
21
+ "@/features/*": ["src/features/*"],
22
+ "@/shared/*": ["src/shared/*"]
23
+ }
24
+ },
25
+ "include": ["src"],
26
+ "references": [{ "path": "./tsconfig.node.json" }]
27
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true,
8
+ "strict": true
9
+ },
10
+ "include": ["vite.config.ts"]
11
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+ import path from "path";
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ "@": path.resolve(__dirname, "./src"),
10
+ },
11
+ },
12
+ });