create-wabck 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.
@@ -0,0 +1,99 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Controller Templates — user.controller.js
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ /** Generate src/controllers/user.controller.js */
6
+ export function userController(options) {
7
+ const { auth, language } = options;
8
+ const isTS = language === 'typescript';
9
+ const hasJwt = auth === 'jwt';
10
+
11
+ const typeImports = isTS
12
+ ? `import { Request, Response } from 'express';\n`
13
+ : '';
14
+
15
+ return `${typeImports}import { asyncHandler } from '../utils/asyncHandler.js';
16
+ import { ApiResponse } from '../utils/apiResponse.js';
17
+ import { UserService } from '../services/user.service.js';
18
+ // ─────────────────────────────────────────────────────────────────────────────
19
+ // User CRUD Controllers
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+
22
+ /** GET /api/v1/users — List all users */
23
+ export const getAllUsers = asyncHandler(async (_req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
24
+ const users = await UserService.getAll();
25
+ res.status(200).json(new ApiResponse(200, users, 'Users fetched successfully'));
26
+ });
27
+
28
+ /** GET /api/v1/users/:id — Get user by ID */
29
+ export const getUserById = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
30
+ const user = await UserService.getById(req.params.id);
31
+ res.status(200).json(new ApiResponse(200, user, 'User fetched successfully'));
32
+ });
33
+
34
+ /** POST /api/v1/users — Create a new user */
35
+ export const createUser = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
36
+ const user = await UserService.create(req.body);
37
+ res.status(201).json(new ApiResponse(201, user, 'User created successfully'));
38
+ });
39
+
40
+ /** PUT /api/v1/users/:id — Update a user */
41
+ export const updateUser = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
42
+ const user = await UserService.update(req.params.id, req.body);
43
+ res.status(200).json(new ApiResponse(200, user, 'User updated successfully'));
44
+ });
45
+
46
+ /** DELETE /api/v1/users/:id — Delete a user */
47
+ export const deleteUser = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
48
+ const result = await UserService.remove(req.params.id);
49
+ res.status(200).json(new ApiResponse(200, result, 'User deleted successfully'));
50
+ });
51
+ ${hasJwt ? `
52
+ // ─────────────────────────────────────────────────────────────────────────────
53
+ // Auth Controllers
54
+ // ─────────────────────────────────────────────────────────────────────────────
55
+
56
+ /** POST /api/v1/auth/register */
57
+ export const register = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
58
+ const result = await UserService.register(req.body);
59
+ res
60
+ .status(201)
61
+ .cookie('refreshToken', result.refreshToken, {
62
+ httpOnly: true,
63
+ secure: process.env.NODE_ENV === 'production',
64
+ sameSite: 'strict',
65
+ maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
66
+ })
67
+ .json(new ApiResponse(201, result, 'Registration successful'));
68
+ });
69
+
70
+ /** POST /api/v1/auth/login */
71
+ export const login = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
72
+ const { email, password } = req.body;
73
+ const result = await UserService.login(email, password);
74
+ res
75
+ .status(200)
76
+ .cookie('refreshToken', result.refreshToken, {
77
+ httpOnly: true,
78
+ secure: process.env.NODE_ENV === 'production',
79
+ sameSite: 'strict',
80
+ maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
81
+ })
82
+ .json(new ApiResponse(200, result, 'Login successful'));
83
+ });
84
+
85
+ /** POST /api/v1/auth/logout */
86
+ export const logout = asyncHandler(async (_req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
87
+ res
88
+ .clearCookie('refreshToken')
89
+ .status(200)
90
+ .json(new ApiResponse(200, null, 'Logged out successfully'));
91
+ });
92
+
93
+ /** GET /api/v1/auth/me — Get current authenticated user */
94
+ export const getMe = asyncHandler(async (req${isTS ? ': Request' : ''}, res${isTS ? ': Response' : ''}) => {
95
+ const user = await UserService.getById((req${isTS ? ' as any' : ''}).user.id);
96
+ res.status(200).json(new ApiResponse(200, user, 'Profile fetched'));
97
+ });
98
+ ` : ''}`;
99
+ }
@@ -0,0 +1,205 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Docker Templates — Dockerfile, docker-compose.yml, .dockerignore
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ /** Generate Dockerfile */
6
+ export function dockerfile(options) {
7
+ const { language, database } = options;
8
+ const isTS = language === 'typescript';
9
+ const port = 5000;
10
+
11
+ if (isTS) {
12
+ return `# ─── Stage 1: Build ──────────────────────────────────────────────────────────
13
+ FROM node:20-alpine AS builder
14
+
15
+ WORKDIR /app
16
+
17
+ COPY package*.json ./
18
+ RUN npm ci
19
+
20
+ COPY . .
21
+ RUN npm run build
22
+
23
+ # ─── Stage 2: Production ─────────────────────────────────────────────────────
24
+ FROM node:20-alpine AS production
25
+
26
+ WORKDIR /app
27
+
28
+ # Create non-root user for security
29
+ RUN addgroup -g 1001 -S nodejs && \\
30
+ adduser -S nodejs -u 1001
31
+
32
+ COPY package*.json ./
33
+ RUN npm ci --omit=dev
34
+
35
+ COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
36
+
37
+ USER nodejs
38
+
39
+ EXPOSE ${port}
40
+
41
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \\
42
+ CMD wget -qO- http://localhost:${port}/api/v1/health || exit 1
43
+
44
+ CMD ["node", "dist/server.js"]
45
+ `;
46
+ }
47
+
48
+ return `# ─── Stage 1: Dependencies ───────────────────────────────────────────────────
49
+ FROM node:20-alpine AS deps
50
+
51
+ WORKDIR /app
52
+ COPY package*.json ./
53
+ RUN npm ci --omit=dev
54
+
55
+ # ─── Stage 2: Production ─────────────────────────────────────────────────────
56
+ FROM node:20-alpine AS production
57
+
58
+ WORKDIR /app
59
+
60
+ # Create non-root user
61
+ RUN addgroup -g 1001 -S nodejs && \\
62
+ adduser -S nodejs -u 1001
63
+
64
+ COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules
65
+ COPY --chown=nodejs:nodejs . .
66
+
67
+ USER nodejs
68
+
69
+ EXPOSE ${port}
70
+
71
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \\
72
+ CMD wget -qO- http://localhost:${port}/api/v1/health || exit 1
73
+
74
+ CMD ["node", "src/server.js"]
75
+ `;
76
+ }
77
+
78
+ /** Generate docker-compose.yml */
79
+ export function dockerCompose(options) {
80
+ const { database, projectName } = options;
81
+ const safe = projectName.toLowerCase().replace(/[^a-z0-9]/g, '_');
82
+
83
+ const dbService = {
84
+ mongodb: `
85
+ mongo:
86
+ image: mongo:7-jammy
87
+ container_name: ${safe}_mongo
88
+ restart: unless-stopped
89
+ ports:
90
+ - "27017:27017"
91
+ environment:
92
+ MONGO_INITDB_DATABASE: ${safe}
93
+ volumes:
94
+ - mongo_data:/data/db
95
+ networks:
96
+ - app_network
97
+ `,
98
+ postgresql: `
99
+ postgres:
100
+ image: postgres:16-alpine
101
+ container_name: ${safe}_postgres
102
+ restart: unless-stopped
103
+ ports:
104
+ - "5432:5432"
105
+ environment:
106
+ POSTGRES_USER: \${DB_USER:-postgres}
107
+ POSTGRES_PASSWORD: \${DB_PASSWORD:-password}
108
+ POSTGRES_DB: \${DB_NAME:-${safe}}
109
+ volumes:
110
+ - postgres_data:/var/lib/postgresql/data
111
+ networks:
112
+ - app_network
113
+ healthcheck:
114
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
115
+ interval: 10s
116
+ timeout: 5s
117
+ retries: 5
118
+ `,
119
+ mysql: `
120
+ mysql:
121
+ image: mysql:8.0
122
+ container_name: ${safe}_mysql
123
+ restart: unless-stopped
124
+ ports:
125
+ - "3306:3306"
126
+ environment:
127
+ MYSQL_ROOT_PASSWORD: \${DB_PASSWORD:-password}
128
+ MYSQL_DATABASE: \${DB_NAME:-${safe}}
129
+ MYSQL_USER: \${DB_USER:-appuser}
130
+ MYSQL_PASSWORD: \${DB_PASSWORD:-password}
131
+ volumes:
132
+ - mysql_data:/var/lib/mysql
133
+ networks:
134
+ - app_network
135
+ healthcheck:
136
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
137
+ interval: 10s
138
+ timeout: 5s
139
+ retries: 5
140
+ `,
141
+ sqlite: '',
142
+ none: '',
143
+ }[database] || '';
144
+
145
+ const dbVolume = {
146
+ mongodb: ' mongo_data:\n',
147
+ postgresql: ' postgres_data:\n',
148
+ mysql: ' mysql_data:\n',
149
+ sqlite: '',
150
+ none: '',
151
+ }[database] || '';
152
+
153
+ const dbDepends = ['mongodb', 'postgresql', 'mysql'].includes(database)
154
+ ? ` depends_on:\n - ${database === 'mongodb' ? 'mongo' : database}\n`
155
+ : '';
156
+
157
+ return `# docker-compose.yml — ${projectName}
158
+ # Usage: docker compose up --build
159
+
160
+ version: '3.9'
161
+
162
+ services:
163
+ app:
164
+ build:
165
+ context: .
166
+ target: production
167
+ container_name: ${safe}_app
168
+ restart: unless-stopped
169
+ ports:
170
+ - "\${PORT:-5000}:5000"
171
+ env_file:
172
+ - .env
173
+ networks:
174
+ - app_network
175
+ ${dbDepends} healthcheck:
176
+ test: ["CMD", "wget", "-qO-", "http://localhost:5000/api/v1/health"]
177
+ interval: 30s
178
+ timeout: 10s
179
+ retries: 3
180
+ start_period: 30s
181
+ ${dbService}
182
+ networks:
183
+ app_network:
184
+ driver: bridge
185
+
186
+ ${dbVolume ? `volumes:\n${dbVolume}` : ''}`;
187
+ }
188
+
189
+ /** Generate .dockerignore */
190
+ export function dockerignore() {
191
+ return `node_modules/
192
+ npm-debug.log*
193
+ .git/
194
+ .gitignore
195
+ .env
196
+ .env.local
197
+ dist/
198
+ coverage/
199
+ *.md
200
+ .dockerignore
201
+ Dockerfile
202
+ docker-compose.yml
203
+ .DS_Store
204
+ `;
205
+ }
@@ -0,0 +1,157 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // GraphQL Templates — Apollo Server 4 integration
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ /** Generate src/graphql/schema.js */
6
+ export function graphqlSchema(options) {
7
+ const { language } = options;
8
+ const isTS = language === 'typescript';
9
+
10
+ return `import { gql } from 'graphql-tag';
11
+
12
+ /**
13
+ * GraphQL Type Definitions
14
+ *
15
+ * Defines the schema for the API including:
16
+ * - User type
17
+ * - Queries (getUsers, getUserById)
18
+ * - Mutations (createUser, updateUser, deleteUser)
19
+ */
20
+ export const typeDefs = gql\`
21
+ type User {
22
+ id: ID!
23
+ name: String!
24
+ email: String!
25
+ role: String!
26
+ isActive: Boolean!
27
+ createdAt: String!
28
+ updatedAt: String!
29
+ }
30
+
31
+ type AuthPayload {
32
+ user: User!
33
+ accessToken: String!
34
+ refreshToken: String!
35
+ }
36
+
37
+ type DeleteResult {
38
+ message: String!
39
+ }
40
+
41
+ input CreateUserInput {
42
+ name: String!
43
+ email: String!
44
+ password: String!
45
+ }
46
+
47
+ input UpdateUserInput {
48
+ name: String
49
+ email: String
50
+ }
51
+
52
+ type Query {
53
+ "Get all active users"
54
+ users: [User!]!
55
+
56
+ "Get a single user by ID"
57
+ user(id: ID!): User
58
+ }
59
+
60
+ type Mutation {
61
+ "Create a new user"
62
+ createUser(input: CreateUserInput!): User!
63
+
64
+ "Update an existing user"
65
+ updateUser(id: ID!, input: UpdateUserInput!): User!
66
+
67
+ "Delete a user"
68
+ deleteUser(id: ID!): DeleteResult!
69
+ }
70
+ \`;
71
+ `;
72
+ }
73
+
74
+ /** Generate src/graphql/resolvers.js */
75
+ export function graphqlResolvers(options) {
76
+ const { language } = options;
77
+ const isTS = language === 'typescript';
78
+
79
+ return `import { UserService } from '../services/user.service.js';
80
+ import { ApiError } from '../utils/apiError.js';
81
+
82
+ /**
83
+ * GraphQL Resolvers
84
+ *
85
+ * Maps GraphQL operations to service layer methods.
86
+ * Errors thrown by services are naturally propagated to Apollo's error handler.
87
+ */
88
+ export const resolvers = {
89
+ Query: {
90
+ users: async (_parent${isTS ? ': unknown' : ''}, _args${isTS ? ': unknown' : ''}, _ctx${isTS ? ': unknown' : ''}) => {
91
+ return UserService.getAll();
92
+ },
93
+
94
+ user: async (_parent${isTS ? ': unknown' : ''}, args${isTS ? ': { id: string }' : ''}, _ctx${isTS ? ': unknown' : ''}) => {
95
+ return UserService.getById(args.id);
96
+ },
97
+ },
98
+
99
+ Mutation: {
100
+ createUser: async (
101
+ _parent${isTS ? ': unknown' : ''},
102
+ args${isTS ? ': { input: { name: string; email: string; password: string } }' : ''},
103
+ _ctx${isTS ? ': unknown' : ''}
104
+ ) => {
105
+ return UserService.create(args.input);
106
+ },
107
+
108
+ updateUser: async (
109
+ _parent${isTS ? ': unknown' : ''},
110
+ args${isTS ? ': { id: string; input: { name?: string; email?: string } }' : ''},
111
+ _ctx${isTS ? ': unknown' : ''}
112
+ ) => {
113
+ return UserService.update(args.id, args.input);
114
+ },
115
+
116
+ deleteUser: async (_parent${isTS ? ': unknown' : ''}, args${isTS ? ': { id: string }' : ''}, _ctx${isTS ? ': unknown' : ''}) => {
117
+ return UserService.remove(args.id);
118
+ },
119
+ },
120
+ };
121
+ `;
122
+ }
123
+
124
+ /** Generate src/graphql/server.js */
125
+ export function graphqlServer(options) {
126
+ const { language } = options;
127
+ const isTS = language === 'typescript';
128
+
129
+ return `import { ApolloServer } from '@apollo/server';
130
+ import { typeDefs } from './schema.js';
131
+ import { resolvers } from './resolvers.js';
132
+
133
+ /**
134
+ * Creates and returns a configured Apollo Server instance.
135
+ * Mount via expressMiddleware in app.js.
136
+ *
137
+ * @returns {ApolloServer}
138
+ */
139
+ export function createApolloServer()${isTS ? ': ApolloServer' : ''} {
140
+ return new ApolloServer({
141
+ typeDefs,
142
+ resolvers,
143
+ introspection: process.env.NODE_ENV !== 'production',
144
+ formatError: (formattedError${isTS ? ': any' : ''}, error${isTS ? ': unknown' : ''}) => {
145
+ // Mask internal server errors in production
146
+ if (
147
+ process.env.NODE_ENV === 'production' &&
148
+ formattedError.extensions?.code === 'INTERNAL_SERVER_ERROR'
149
+ ) {
150
+ return { message: 'Internal server error' };
151
+ }
152
+ return formattedError;
153
+ },
154
+ });
155
+ }
156
+ `;
157
+ }
@@ -0,0 +1,117 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Helper Templates — token.helper.js, constants, etc.
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ /** Generate src/helpers/token.helper.js (JWT only) */
6
+ export function tokenHelper(options) {
7
+ const { language } = options;
8
+ const isTS = language === 'typescript';
9
+
10
+ return `import jwt from 'jsonwebtoken';
11
+ import { ApiError } from '../utils/apiError.js';
12
+
13
+ ${isTS ? `
14
+ interface TokenPayload {
15
+ id: string;
16
+ email: string;
17
+ role: string;
18
+ }
19
+
20
+ interface TokenPair {
21
+ accessToken: string;
22
+ refreshToken: string;
23
+ }
24
+ ` : ''}
25
+ const ACCESS_TOKEN_EXPIRY = process.env.JWT_EXPIRES_IN || '15m';
26
+ const REFRESH_TOKEN_EXPIRY = process.env.JWT_REFRESH_EXPIRES_IN || '30d';
27
+
28
+ /**
29
+ * Generates an access token + refresh token pair for the given payload.
30
+ *
31
+ * Access tokens are short-lived (15 min by default).
32
+ * Refresh tokens are long-lived (30 days by default).
33
+ *
34
+ * @param payload - Data to embed in the token { id, email, role }
35
+ * @returns {{ accessToken, refreshToken }}
36
+ */
37
+ export function generateTokens(payload${isTS ? ': TokenPayload' : ''})${isTS ? ': TokenPair' : ''} {
38
+ const secret = process.env.JWT_SECRET;
39
+ if (!secret) throw new ApiError(500, 'JWT_SECRET is not configured');
40
+
41
+ const accessToken = jwt.sign(payload, secret, {
42
+ expiresIn: ACCESS_TOKEN_EXPIRY,
43
+ });
44
+
45
+ const refreshToken = jwt.sign({ id: payload.id }, secret, {
46
+ expiresIn: REFRESH_TOKEN_EXPIRY,
47
+ });
48
+
49
+ return { accessToken, refreshToken };
50
+ }
51
+
52
+ /**
53
+ * Verifies a JWT token and returns the decoded payload.
54
+ * Throws an ApiError on invalid or expired tokens.
55
+ *
56
+ * @param token - The raw JWT string
57
+ * @returns Decoded payload
58
+ */
59
+ export function verifyToken(token${isTS ? ': string' : ''})${isTS ? ': TokenPayload & jwt.JwtPayload' : ''} {
60
+ const secret = process.env.JWT_SECRET;
61
+ if (!secret) throw new ApiError(500, 'JWT_SECRET is not configured');
62
+
63
+ try {
64
+ return jwt.verify(token, secret)${isTS ? ' as TokenPayload & jwt.JwtPayload' : ''};
65
+ } catch (error) {
66
+ if ((error${isTS ? ' as Error' : ''}).name === 'TokenExpiredError') {
67
+ throw new ApiError(401, 'Token has expired. Please log in again.');
68
+ }
69
+ throw new ApiError(401, 'Invalid token. Please log in again.');
70
+ }
71
+ }
72
+ `;
73
+ }
74
+
75
+ /** Generate src/constants/index.js */
76
+ export function constants(options) {
77
+ const { language } = options;
78
+ const isTS = language === 'typescript';
79
+
80
+ return `// ─────────────────────────────────────────────────────────────────────────────
81
+ // Application-Wide Constants
82
+ // ─────────────────────────────────────────────────────────────────────────────
83
+
84
+ /** HTTP Status Codes — avoids magic numbers in controllers/services */
85
+ export const HTTP_STATUS = {
86
+ OK: 200,
87
+ CREATED: 201,
88
+ NO_CONTENT: 204,
89
+ BAD_REQUEST: 400,
90
+ UNAUTHORIZED: 401,
91
+ FORBIDDEN: 403,
92
+ NOT_FOUND: 404,
93
+ CONFLICT: 409,
94
+ UNPROCESSABLE_ENTITY: 422,
95
+ TOO_MANY_REQUESTS: 429,
96
+ INTERNAL_SERVER_ERROR: 500,
97
+ }${isTS ? ' as const' : ''};
98
+
99
+ /** User roles */
100
+ export const ROLES = {
101
+ USER: 'user',
102
+ ADMIN: 'admin',
103
+ }${isTS ? ' as const' : ''};
104
+
105
+ /** Default pagination settings */
106
+ export const PAGINATION = {
107
+ DEFAULT_PAGE: 1,
108
+ DEFAULT_LIMIT: 10,
109
+ MAX_LIMIT: 100,
110
+ }${isTS ? ' as const' : ''};
111
+
112
+ /** Cookie names */
113
+ export const COOKIES = {
114
+ REFRESH_TOKEN: 'refreshToken',
115
+ }${isTS ? ' as const' : ''};
116
+ `;
117
+ }
@@ -0,0 +1,78 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // Template Index — Re-exports all template functions
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ export {
6
+ app,
7
+ server,
8
+ envFile,
9
+ envExample,
10
+ gitignore,
11
+ prettierrc,
12
+ eslintConfig,
13
+ readme,
14
+ } from './base.js';
15
+
16
+ export {
17
+ db,
18
+ envConfig,
19
+ } from './config.js';
20
+
21
+ export {
22
+ routesIndex,
23
+ healthRoute,
24
+ userRoute,
25
+ authRoute,
26
+ } from './routes.js';
27
+
28
+ export {
29
+ userController,
30
+ } from './controllers.js';
31
+
32
+ export {
33
+ userModel,
34
+ prismaSchema,
35
+ } from './models.js';
36
+
37
+ export {
38
+ errorMiddleware,
39
+ notFoundMiddleware,
40
+ rateLimiterMiddleware,
41
+ authMiddleware,
42
+ } from './middlewares.js';
43
+
44
+ export {
45
+ userService,
46
+ } from './services.js';
47
+
48
+ export {
49
+ asyncHandler,
50
+ apiError,
51
+ apiResponse,
52
+ } from './utils.js';
53
+
54
+ export {
55
+ userValidator,
56
+ } from './validators.js';
57
+
58
+ export {
59
+ tokenHelper,
60
+ constants,
61
+ } from './helpers.js';
62
+
63
+ export {
64
+ dockerfile,
65
+ dockerCompose,
66
+ dockerignore,
67
+ } from './docker.js';
68
+
69
+ export {
70
+ tsconfig,
71
+ tsTypes,
72
+ } from './typescript.js';
73
+
74
+ export {
75
+ graphqlSchema,
76
+ graphqlResolvers,
77
+ graphqlServer,
78
+ } from './graphql.js';