create-ely 0.1.1

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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/package.json +60 -0
  4. package/src/index.ts +187 -0
  5. package/templates/monorepo/README.md +75 -0
  6. package/templates/monorepo/apps/backend/.cursor/mcp.json +8 -0
  7. package/templates/monorepo/apps/backend/.dockerignore +60 -0
  8. package/templates/monorepo/apps/backend/.env.example +19 -0
  9. package/templates/monorepo/apps/backend/.github/workflows/lint.yml +31 -0
  10. package/templates/monorepo/apps/backend/.github/workflows/tests.yml +36 -0
  11. package/templates/monorepo/apps/backend/AGENTS.md +79 -0
  12. package/templates/monorepo/apps/backend/CHANGELOG.md +190 -0
  13. package/templates/monorepo/apps/backend/CLAUDE.md +149 -0
  14. package/templates/monorepo/apps/backend/Dockerfile +35 -0
  15. package/templates/monorepo/apps/backend/LICENSE +21 -0
  16. package/templates/monorepo/apps/backend/README.md +274 -0
  17. package/templates/monorepo/apps/backend/biome.json +58 -0
  18. package/templates/monorepo/apps/backend/bun.lock +303 -0
  19. package/templates/monorepo/apps/backend/docker-compose.yml +37 -0
  20. package/templates/monorepo/apps/backend/drizzle.config.ts +14 -0
  21. package/templates/monorepo/apps/backend/package.json +42 -0
  22. package/templates/monorepo/apps/backend/src/common/config.ts +29 -0
  23. package/templates/monorepo/apps/backend/src/common/logger.ts +18 -0
  24. package/templates/monorepo/apps/backend/src/db/index.ts +31 -0
  25. package/templates/monorepo/apps/backend/src/db/migrations/20251111132328_curly_spectrum.sql +8 -0
  26. package/templates/monorepo/apps/backend/src/db/migrations/meta/20251111132328_snapshot.json +70 -0
  27. package/templates/monorepo/apps/backend/src/db/migrations/meta/_journal.json +13 -0
  28. package/templates/monorepo/apps/backend/src/db/schema/users.ts +39 -0
  29. package/templates/monorepo/apps/backend/src/main.ts +67 -0
  30. package/templates/monorepo/apps/backend/src/middleware/error-handler.ts +36 -0
  31. package/templates/monorepo/apps/backend/src/modules/users/index.ts +61 -0
  32. package/templates/monorepo/apps/backend/src/modules/users/model.ts +48 -0
  33. package/templates/monorepo/apps/backend/src/modules/users/service.ts +46 -0
  34. package/templates/monorepo/apps/backend/src/tests/users.test.ts +102 -0
  35. package/templates/monorepo/apps/backend/src/util/graceful-shutdown.ts +37 -0
  36. package/templates/monorepo/apps/backend/tsconfig.json +35 -0
  37. package/templates/monorepo/apps/backend-biome.json.template +14 -0
  38. package/templates/monorepo/apps/frontend/.env.example +1 -0
  39. package/templates/monorepo/apps/frontend/README.md +59 -0
  40. package/templates/monorepo/apps/frontend/biome.json +16 -0
  41. package/templates/monorepo/apps/frontend/components.json +21 -0
  42. package/templates/monorepo/apps/frontend/index.html +15 -0
  43. package/templates/monorepo/apps/frontend/package.json +48 -0
  44. package/templates/monorepo/apps/frontend/public/favicon.ico +0 -0
  45. package/templates/monorepo/apps/frontend/src/assets/fonts/.gitkeep +0 -0
  46. package/templates/monorepo/apps/frontend/src/assets/images/.gitkeep +0 -0
  47. package/templates/monorepo/apps/frontend/src/features/layout/Header.tsx +73 -0
  48. package/templates/monorepo/apps/frontend/src/main.tsx +36 -0
  49. package/templates/monorepo/apps/frontend/src/routeTree.gen.ts +95 -0
  50. package/templates/monorepo/apps/frontend/src/routes/__root.tsx +25 -0
  51. package/templates/monorepo/apps/frontend/src/routes/index.tsx +34 -0
  52. package/templates/monorepo/apps/frontend/src/routes/users/index.tsx +79 -0
  53. package/templates/monorepo/apps/frontend/src/routes/users/new.tsx +148 -0
  54. package/templates/monorepo/apps/frontend/src/shared/api/client.ts +6 -0
  55. package/templates/monorepo/apps/frontend/src/shared/components/.gitkeep +0 -0
  56. package/templates/monorepo/apps/frontend/src/shared/constants/.gitkeep +0 -0
  57. package/templates/monorepo/apps/frontend/src/shared/hooks/.gitkeep +0 -0
  58. package/templates/monorepo/apps/frontend/src/shared/types/.gitkeep +0 -0
  59. package/templates/monorepo/apps/frontend/src/shared/utils/utils.ts +6 -0
  60. package/templates/monorepo/apps/frontend/src/styles.css +138 -0
  61. package/templates/monorepo/apps/frontend/src/vite-env.d.ts +13 -0
  62. package/templates/monorepo/apps/frontend/tsconfig.json +27 -0
  63. package/templates/monorepo/apps/frontend/vite.config.ts +27 -0
  64. package/templates/monorepo/biome.json +65 -0
  65. package/templates/monorepo/bun.lock +1044 -0
  66. package/templates/monorepo/package.json +13 -0
@@ -0,0 +1,70 @@
1
+ {
2
+ "id": "ae4e6c14-3efe-4132-b1c4-cc83dccf17d4",
3
+ "prevId": "00000000-0000-0000-0000-000000000000",
4
+ "version": "7",
5
+ "dialect": "postgresql",
6
+ "tables": {
7
+ "public.users": {
8
+ "name": "users",
9
+ "schema": "",
10
+ "columns": {
11
+ "id": {
12
+ "name": "id",
13
+ "type": "uuid",
14
+ "primaryKey": true,
15
+ "notNull": true
16
+ },
17
+ "name": {
18
+ "name": "name",
19
+ "type": "varchar(255)",
20
+ "primaryKey": false,
21
+ "notNull": true
22
+ },
23
+ "surname": {
24
+ "name": "surname",
25
+ "type": "varchar(255)",
26
+ "primaryKey": false,
27
+ "notNull": true
28
+ },
29
+ "email": {
30
+ "name": "email",
31
+ "type": "varchar(255)",
32
+ "primaryKey": false,
33
+ "notNull": true
34
+ },
35
+ "created_at": {
36
+ "name": "created_at",
37
+ "type": "timestamp",
38
+ "primaryKey": false,
39
+ "notNull": true,
40
+ "default": "now()"
41
+ },
42
+ "updated_at": {
43
+ "name": "updated_at",
44
+ "type": "timestamp",
45
+ "primaryKey": false,
46
+ "notNull": true,
47
+ "default": "now()"
48
+ }
49
+ },
50
+ "indexes": {},
51
+ "foreignKeys": {},
52
+ "compositePrimaryKeys": {},
53
+ "uniqueConstraints": {},
54
+ "policies": {},
55
+ "checkConstraints": {},
56
+ "isRLSEnabled": false
57
+ }
58
+ },
59
+ "enums": {},
60
+ "schemas": {},
61
+ "sequences": {},
62
+ "roles": {},
63
+ "policies": {},
64
+ "views": {},
65
+ "_meta": {
66
+ "columns": {},
67
+ "schemas": {},
68
+ "tables": {}
69
+ }
70
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "postgresql",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "7",
8
+ "when": 1762867408207,
9
+ "tag": "20251111132328_curly_spectrum",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,39 @@
1
+ import { pgTable, timestamp, uuid, varchar } from 'drizzle-orm/pg-core';
2
+ import {
3
+ createInsertSchema,
4
+ createSelectSchema,
5
+ createUpdateSchema,
6
+ } from 'drizzle-typebox';
7
+ import { t } from 'elysia';
8
+
9
+ // Users table definition
10
+ export const users = pgTable('users', {
11
+ id: uuid('id')
12
+ .primaryKey()
13
+ .$defaultFn(() => Bun.randomUUIDv7()),
14
+ name: varchar('name', { length: 255 }).notNull(),
15
+ surname: varchar('surname', { length: 255 }).notNull(),
16
+ email: varchar('email', { length: 255 }).notNull(),
17
+ createdAt: timestamp('created_at').notNull().defaultNow(),
18
+ updatedAt: timestamp('updated_at').notNull().defaultNow(),
19
+ });
20
+
21
+ // Field validation refinements for Elysia
22
+ const fieldRefinements = {
23
+ name: t.String({ minLength: 1, maxLength: 255, examples: ['User Name'] }),
24
+ surname: t.String({
25
+ minLength: 1,
26
+ maxLength: 255,
27
+ examples: ['User Surname'],
28
+ }),
29
+ email: t.String({ minLength: 1, maxLength: 255, examples: ['User Email'] }),
30
+ };
31
+
32
+ // Drizzle-TypeBox schemas (declare variables to avoid infinite type instantiation)
33
+ const _userCreate = createInsertSchema(users, fieldRefinements);
34
+ const _userSelect = createSelectSchema(users, fieldRefinements);
35
+ const _userUpdate = createUpdateSchema(users, fieldRefinements);
36
+
37
+ export const userCreate = _userCreate;
38
+ export const userSelect = _userSelect;
39
+ export const userUpdate = _userUpdate;
@@ -0,0 +1,67 @@
1
+ import cors from '@elysiajs/cors';
2
+ import openapi from '@elysiajs/openapi';
3
+ import { Elysia } from 'elysia';
4
+ import config from './common/config';
5
+ import { log } from './common/logger';
6
+ import { migrateDb } from './db';
7
+ import { errorHandler } from './middleware/error-handler';
8
+ import { users } from './modules/users';
9
+ import { gracefulShutdown } from './util/graceful-shutdown';
10
+
11
+ const app = new Elysia()
12
+ .use(cors())
13
+ .use(errorHandler)
14
+ .use(
15
+ openapi({
16
+ documentation: {
17
+ info: {
18
+ title: 'Elysia Boilerplate',
19
+ version: '0.1.5',
20
+ description: 'A simple boilerplate service for Elysia',
21
+ },
22
+ servers: [
23
+ {
24
+ url: `http://${config.SERVER_HOSTNAME}:${config.SERVER_PORT}`,
25
+ description: 'Local development server',
26
+ },
27
+ ],
28
+ },
29
+ enabled: config.ENABLE_OPENAPI,
30
+ }),
31
+ )
32
+ .use(users);
33
+
34
+ /**
35
+ * Bootstrap the application.
36
+ * Runs all initialization tasks before starting the server.
37
+ */
38
+ async function bootstrap(): Promise<void> {
39
+ // Run database migrations before accepting any requests
40
+ if (config.DB_AUTO_MIGRATE) {
41
+ await migrateDb();
42
+ }
43
+
44
+ // Start the server only after all initialization is complete
45
+ app.listen(config.SERVER_PORT, ({ development, hostname, port }) => {
46
+ log.info(
47
+ `🦊 Elysia is running at http://${hostname}:${port} ${development ? '🚧 in development mode!🚧' : ''}`,
48
+ );
49
+ if (config.ENABLE_OPENAPI) {
50
+ log.info(
51
+ `📚 OpenAPI documentation is available at http://${hostname}:${port}/openapi`,
52
+ );
53
+ } else {
54
+ log.info('📚 OpenAPI documentation is disabled');
55
+ }
56
+ });
57
+
58
+ process.once('SIGINT', () => gracefulShutdown(app, 'SIGINT'));
59
+ process.once('SIGTERM', () => gracefulShutdown(app, 'SIGTERM'));
60
+ }
61
+
62
+ bootstrap().catch((error) => {
63
+ log.fatal({ err: error }, 'Failed to start application');
64
+ process.exit(1);
65
+ });
66
+
67
+ export type App = typeof app;
@@ -0,0 +1,36 @@
1
+ import { Elysia, ElysiaCustomStatusResponse, status } from 'elysia';
2
+ import { log as logger } from 'src/common/logger';
3
+
4
+ const log = logger.child({ name: 'error-handler' });
5
+
6
+ /**
7
+ * Global error handling middleware
8
+ * Catches all unhandled errors and logs them
9
+ */
10
+ export const errorHandler = new Elysia({ name: 'error-handler' }).onError(
11
+ ({ code, error, request }) => {
12
+ // Return Elysia's handled errors as-is
13
+ if (error instanceof ElysiaCustomStatusResponse || code !== 'UNKNOWN') {
14
+ return error;
15
+ }
16
+
17
+ // Log unhandled errors
18
+ log.error(
19
+ {
20
+ code,
21
+ err: error,
22
+ http: request
23
+ ? {
24
+ method: request.method,
25
+ url: request.url,
26
+ referrer: request.headers.get('referer') ?? undefined,
27
+ }
28
+ : undefined,
29
+ },
30
+ 'Unhandled error',
31
+ );
32
+
33
+ // Do not expose unhandled errors to the client
34
+ return status(500, 'Internal Server Error');
35
+ },
36
+ );
@@ -0,0 +1,61 @@
1
+ import { Elysia, status } from 'elysia';
2
+ import { log as logger } from 'src/common/logger';
3
+ import { type UsersModel, usersModelPlugin } from './model';
4
+ import { UsersService } from './service';
5
+
6
+ const log = logger.child({ name: 'users' });
7
+
8
+ export const users = new Elysia({ prefix: '/users', tags: ['Users'] })
9
+ .use(usersModelPlugin)
10
+ .post(
11
+ '/',
12
+ async ({ body }): Promise<UsersModel.createResponse> => {
13
+ try {
14
+ const user = await UsersService.create(body);
15
+ log.info(`Created user ${user.name}`);
16
+ return user;
17
+ } catch (error) {
18
+ log.error({ err: error }, 'Failed to create user');
19
+ throw status(422, {
20
+ message: 'Failed to create user' satisfies UsersModel.createError,
21
+ });
22
+ }
23
+ },
24
+ {
25
+ body: 'users.createRequest',
26
+ response: {
27
+ 200: 'users.createResponse',
28
+ 422: 'users.createError',
29
+ },
30
+ detail: {
31
+ summary: 'Create a new user',
32
+ description: 'Create a new user in the database with name and surname.',
33
+ },
34
+ },
35
+ )
36
+ .get(
37
+ '/',
38
+ async ({ query }): Promise<UsersModel.getResponse> => {
39
+ try {
40
+ const users = await UsersService.get(query);
41
+ log.info(`Got users ${users.total}`);
42
+ return users;
43
+ } catch (error) {
44
+ log.error({ err: error }, 'Failed to get users');
45
+ throw status(422, {
46
+ message: 'Failed to get users' satisfies UsersModel.getError,
47
+ });
48
+ }
49
+ },
50
+ {
51
+ query: 'users.getQuery',
52
+ response: {
53
+ 200: 'users.getResponse',
54
+ 422: 'users.getError',
55
+ },
56
+ detail: {
57
+ summary: 'Get all users',
58
+ description: 'Get all users from the database with name and surname.',
59
+ },
60
+ },
61
+ );
@@ -0,0 +1,48 @@
1
+ import Elysia, { t } from 'elysia';
2
+ import { userCreate, userSelect } from '../../db/schema/users';
3
+
4
+ export namespace UsersModel {
5
+ // Create user
6
+ export const createRequest = t.Omit(userCreate, [
7
+ 'id',
8
+ 'createdAt',
9
+ 'updatedAt',
10
+ ]);
11
+ export type createRequest = typeof createRequest.static;
12
+
13
+ export const createResponse = t.Omit(userSelect, ['createdAt', 'updatedAt']);
14
+ export type createResponse = typeof createResponse.static;
15
+
16
+ export const createError = t.Literal('Failed to create user');
17
+ export type createError = typeof createError.static;
18
+
19
+ // Get users
20
+ export const getQuery = t.Object({
21
+ limit: t.Number({
22
+ default: 100,
23
+ minimum: 1,
24
+ maximum: 100,
25
+ examples: [100],
26
+ }),
27
+ offset: t.Number({ default: 0, minimum: 0, examples: [0] }),
28
+ });
29
+ export type getQuery = typeof getQuery.static;
30
+
31
+ export const getResponse = t.Object({
32
+ users: t.Array(t.Omit(userSelect, ['createdAt', 'updatedAt'])),
33
+ total: t.Number(),
34
+ });
35
+ export type getResponse = typeof getResponse.static;
36
+
37
+ export const getError = t.Literal('Failed to get users');
38
+ export type getError = typeof getError.static;
39
+ }
40
+
41
+ export const usersModelPlugin = new Elysia().model({
42
+ 'users.createRequest': UsersModel.createRequest,
43
+ 'users.createResponse': UsersModel.createResponse,
44
+ 'users.createError': UsersModel.createError,
45
+ 'users.getQuery': UsersModel.getQuery,
46
+ 'users.getResponse': UsersModel.getResponse,
47
+ 'users.getError': UsersModel.getError,
48
+ });
@@ -0,0 +1,46 @@
1
+ import { count } from 'drizzle-orm';
2
+ import { status } from 'elysia';
3
+ import db from '../../db';
4
+ import { users } from '../../db/schema/users';
5
+ import type { UsersModel } from './model';
6
+
7
+ export abstract class UsersService {
8
+ static async create(
9
+ body: UsersModel.createRequest,
10
+ ): Promise<UsersModel.createResponse> {
11
+ const [user] = await db
12
+ .insert(users)
13
+ .values({ ...body })
14
+ .returning();
15
+
16
+ if (!user) {
17
+ throw status(422, {
18
+ message: 'Failed to create user' satisfies UsersModel.createError,
19
+ });
20
+ }
21
+
22
+ return user;
23
+ }
24
+
25
+ static async get(
26
+ params: UsersModel.getQuery,
27
+ ): Promise<UsersModel.getResponse> {
28
+ const [total] = await db.select({ count: count() }).from(users);
29
+
30
+ const res = await db
31
+ .select({
32
+ id: users.id,
33
+ name: users.name,
34
+ surname: users.surname,
35
+ email: users.email,
36
+ })
37
+ .from(users)
38
+ .limit(params.limit)
39
+ .offset(params.offset);
40
+
41
+ return {
42
+ users: res,
43
+ total: total?.count ?? 0,
44
+ };
45
+ }
46
+ }
@@ -0,0 +1,102 @@
1
+ import { beforeEach, describe, expect, it, mock } from 'bun:test';
2
+ import type { UsersModel } from 'src/modules/users/model';
3
+ import { users } from '../modules/users/index';
4
+ import { UsersService } from '../modules/users/service';
5
+
6
+ // Mock the UsersService
7
+ const mockUsersService = {
8
+ create: mock(() =>
9
+ Promise.resolve({
10
+ id: '0199477e-7aa7-7000-a65a-96e2efd46c10',
11
+ name: 'John',
12
+ surname: 'Doe',
13
+ email: 'john.doe@example.com',
14
+ }),
15
+ ),
16
+ get: mock(() =>
17
+ Promise.resolve({
18
+ users: [
19
+ {
20
+ id: '0199477e-7aa7-7000-a65a-96e2efd46c10',
21
+ name: 'John',
22
+ surname: 'Doe',
23
+ email: 'john.doe@example.com',
24
+ },
25
+ ],
26
+ total: 1,
27
+ }),
28
+ ),
29
+ };
30
+
31
+ // Mock the service methods
32
+ Object.assign(UsersService, mockUsersService);
33
+
34
+ describe('Users Module', () => {
35
+ beforeEach(() => {
36
+ mockUsersService.create.mockClear();
37
+ mockUsersService.get.mockClear();
38
+ });
39
+
40
+ describe('POST /users', () => {
41
+ it('should create a new user successfully', async () => {
42
+ const userData = {
43
+ name: 'John',
44
+ surname: 'Doe',
45
+ email: 'john.doe@example.com',
46
+ };
47
+
48
+ const response = await users.handle(
49
+ new Request('http://localhost/users', {
50
+ method: 'POST',
51
+ headers: { 'Content-Type': 'application/json' },
52
+ body: JSON.stringify(userData),
53
+ }),
54
+ );
55
+ expect(response.status).toBe(200);
56
+
57
+ const responseData = await response.json();
58
+ expect(responseData).toHaveProperty('id');
59
+ expect(responseData).toHaveProperty('name', 'John');
60
+ expect(responseData).toHaveProperty('surname', 'Doe');
61
+ expect(responseData).toHaveProperty('email', 'john.doe@example.com');
62
+
63
+ // Verify the service was called with correct data
64
+ expect(mockUsersService.create).toHaveBeenCalledWith(userData);
65
+ });
66
+
67
+ it('should return validation error for missing required fields', async () => {
68
+ const invalidUserData = {
69
+ name: 'John',
70
+ // missing surname and email
71
+ };
72
+
73
+ const response = await users.handle(
74
+ new Request('http://localhost/users', {
75
+ method: 'POST',
76
+ headers: { 'Content-Type': 'application/json' },
77
+ body: JSON.stringify(invalidUserData),
78
+ }),
79
+ );
80
+ expect(response.status).toBe(422);
81
+
82
+ expect(mockUsersService.create).not.toHaveBeenCalled();
83
+ });
84
+ });
85
+
86
+ describe('GET /users', () => {
87
+ it('should get all users successfully', async () => {
88
+ const response = await users.handle(
89
+ new Request('http://localhost/users'),
90
+ );
91
+ expect(response.status).toBe(200);
92
+
93
+ const responseData = (await response.json()) as UsersModel.getResponse;
94
+ expect(responseData).toHaveProperty('users');
95
+ expect(responseData).toHaveProperty('total');
96
+ expect(Array.isArray(responseData.users)).toBe(true);
97
+ expect(typeof responseData.total).toBe('number');
98
+
99
+ expect(mockUsersService.get).toHaveBeenCalled();
100
+ });
101
+ });
102
+ });
@@ -0,0 +1,37 @@
1
+ import { log as logger } from 'src/common/logger';
2
+ import db from 'src/db';
3
+ import type { App } from 'src/main';
4
+
5
+ const log = logger.child({ name: 'graceful-shutdown' });
6
+
7
+ let isShuttingDown = false;
8
+
9
+ export async function gracefulShutdown(
10
+ app: App,
11
+ signal: NodeJS.Signals,
12
+ ): Promise<void> {
13
+ if (isShuttingDown) {
14
+ log.warn('Already shutting down, ignoring signal');
15
+ return;
16
+ }
17
+
18
+ isShuttingDown = true;
19
+
20
+ log.info(`${signal} received, shutting down...`);
21
+
22
+ const shutdownTimeout = setTimeout(() => {
23
+ log.error('Shutdown timeout exceeded, forcing exit');
24
+ process.exit(1);
25
+ }, 10000); // 10 second timeout
26
+
27
+ try {
28
+ await app.stop();
29
+ await db.$client.end();
30
+ clearTimeout(shutdownTimeout);
31
+ process.exit(0);
32
+ } catch (error) {
33
+ clearTimeout(shutdownTimeout);
34
+ log.error(error, `Error during ${signal} shutdown`);
35
+ process.exit(1);
36
+ }
37
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": true,
22
+ "noImplicitOverride": true,
23
+
24
+ // Some stricter flags (disabled by default)
25
+ "noUnusedLocals": false,
26
+ "noUnusedParameters": false,
27
+ "noPropertyAccessFromIndexSignature": false,
28
+
29
+ // Paths
30
+ "baseUrl": "."
31
+ },
32
+
33
+ "include": ["src/**/*"],
34
+ "exclude": ["node_modules", "dist"]
35
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "root": false,
3
+ "$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
4
+ "extends": ["../../biome.json"],
5
+ "files": {
6
+ "includes": [
7
+ "**/src/**/*",
8
+ "**/.vscode/**/*",
9
+ "**/drizzle.config.ts",
10
+ "**/tsconfig.json",
11
+ "!**/src/db/migrations/**/*"
12
+ ]
13
+ }
14
+ }
@@ -0,0 +1 @@
1
+ VITE_API_URL=http://localhost:3000
@@ -0,0 +1,59 @@
1
+ # Frontend Boilerplate
2
+
3
+ React boilerplate for ElysiaJS full-stack applications.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ bun install
9
+ bun run dev # Start dev server on port 5173
10
+ bun run build # Build for production
11
+ ```
12
+
13
+ ## Stack
14
+
15
+ - **React 19** with TypeScript
16
+ - **TanStack Router** - File-based routing in `src/routes/`
17
+ - **Tailwind CSS** - Styling with v4
18
+ - **Biome** - Linting & formatting
19
+ - **Vitest** - Testing
20
+
21
+ ## Project Structure
22
+
23
+ ```
24
+ src/
25
+ ├── features/ # Feature modules (e.g., layout)
26
+ ├── shared/ # Shared code
27
+ │ ├── components/ # Reusable UI components
28
+ │ ├── hooks/ # Custom hooks
29
+ │ ├── utils/ # Utilities
30
+ │ ├── types/ # Type definitions
31
+ │ └── constants/ # App constants
32
+ ├── assets/ # Images, fonts, etc.
33
+ ├── routes/ # File-based routes
34
+ └── main.tsx
35
+ ```
36
+
37
+ ## Commands
38
+
39
+ ```bash
40
+ bun run dev # Development server
41
+ bun run build # Production build
42
+ bun run preview # Preview production build
43
+ bun run test # Run tests
44
+ bun run lint # Lint code
45
+ bun run format # Format code
46
+ bun run check # Lint + format check
47
+ ```
48
+
49
+ ## Adding Routes
50
+
51
+ Create a new file in `src/routes/` - TanStack Router handles the rest automatically.
52
+
53
+ ## Shadcn UI
54
+
55
+ Add components:
56
+
57
+ ```bash
58
+ bunx shadcn@latest add button
59
+ ```
@@ -0,0 +1,16 @@
1
+ {
2
+ "root": false,
3
+ "$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
4
+ "extends": ["../../biome.json"],
5
+ "files": {
6
+ "includes": [
7
+ "**/src/**/*",
8
+ "**/.vscode/**/*",
9
+ "**/index.html",
10
+ "**/vite.config.js",
11
+ "!**/src/routeTree.gen.ts",
12
+ "!**/src/styles.css",
13
+ "!**/src/vite-env.d.ts"
14
+ ]
15
+ }
16
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/styles.css",
9
+ "baseColor": "zinc",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }