nhb-express
Version:
Minimal Express + TypeScript scaffolder with modular structure and flexible stack choices.
252 lines (206 loc) • 8.22 kB
JavaScript
// @ts-check
import { capitalizeString, pluralizer } from 'nhb-toolbox';
/**
* * Generate module for Drizzle/Postgres based Express TS Server.
* @param {string} moduleName Name of the module.
* @returns Array of objects (file names and contents).
*/
export function expressDrizzlePostgresTemplate(moduleName) {
const capModule = capitalizeString(moduleName);
const pluralModule = pluralizer.toPlural(moduleName);
const pluralCapModule = pluralizer.toPlural(capModule);
const baseAlias = '@';
const moduleAlias = `@/modules/${moduleName}`;
return [
// ! module.route.ts
{
name: `${moduleName}.routes.ts`,
content: `import validateRequest from '${baseAlias}/middlewares/validateRequest';
import { ${moduleName}Controllers } from '${moduleAlias}/${moduleName}.controllers';
import { ${moduleName}Validations } from '${moduleAlias}/${moduleName}.validation';
import { Router } from 'express';
const router = Router();
router.post(
'/',
validateRequest(${moduleName}Validations.creationSchema),
${moduleName}Controllers.create${capModule},
);
router.get('/', ${moduleName}Controllers.getAll${pluralCapModule});
router.get('/:id', ${moduleName}Controllers.get${capModule}ById);
router.patch(
'/:id',
validateRequest(${moduleName}Validations.updateSchema),
${moduleName}Controllers.update${capModule}ById,
);
router.delete('/:id', ${moduleName}Controllers.delete${capModule}ById);
export const ${moduleName}Routes = router;
`,
},
// ! module.controllers.ts
{
name: `${moduleName}.controllers.ts`,
content: `import { ${moduleName}Services } from '${moduleAlias}/${moduleName}.services';
import catchAsync from '${baseAlias}/utilities/catchAsync';
import sendResponse from '${baseAlias}/utilities/sendResponse';
class ${capModule}Controllers {
/** * Create ${moduleName}. */
create${capModule} = catchAsync(async (req, res) => {
const ${moduleName} = await ${moduleName}Services.create${capModule}InDB(req.query);
sendResponse(res, '${capModule}', 'POST', ${moduleName});
});
/** * Get all ${pluralModule} with optional queries. */
getAll${pluralCapModule} = catchAsync(async (req, res) => {
const ${pluralModule} = await ${moduleName}Services.getAll${pluralCapModule}FromDB(req.query);
sendResponse(res, '${capModule}', 'GET', ${pluralModule});
});
/** * Get ${moduleName} by ${moduleName} id. */
get${capModule}ById = catchAsync(async (req, res) => {
const ${moduleName} = await ${moduleName}Services.get${capModule}ByIdFromDB(Number(req.params.id));
sendResponse(res, '${capModule}', 'GET', ${moduleName});
});
/** * Delete ${moduleName} by id. */
delete${capModule}ById = catchAsync(async (req, res) => {
const result = await ${moduleName}Services.delete${capModule}ByIdFromDB(Number(req.params.id));
sendResponse(res, '${capModule}', 'DELETE', result);
});
/** * Update ${moduleName} by id. */
update${capModule}ById = catchAsync(async (req, res) => {
const result = await ${moduleName}Services.update${capModule}InDB(Number(req.params.id), req.body);
sendResponse(res, '${capModule}', 'PATCH', result);
});
}
export const ${moduleName}Controllers = new ${capModule}Controllers();
`,
},
// ! module.services.ts
{
name: `${moduleName}.services.ts`,
content: `import { db } from '#/drizzle';
import { ${pluralModule} } from '#/drizzle/schema/${pluralModule}';
import { ErrorWithStatus } from '${baseAlias}/errors/ErrorWithStatus';
import type { Insert${capModule}, Update${capModule} } from '${moduleAlias}/${moduleName}.types';
import type { TQueries } from '${baseAlias}/types';
import { eq } from 'drizzle-orm';
import { isNotEmptyObject } from 'nhb-toolbox';
import { STATUS_CODES } from 'nhb-toolbox/constants';
class ${capModule}Services {
/**
* * Create new ${moduleName} in the DB.
* @param payload All the required fields to create ${moduleName}.
* @returns Created new ${moduleName}.
*/
async create${capModule}InDB(payload: Insert${capModule}) {
const [new${capModule}] = await db.insert(${pluralModule}).values(payload).returning();
if (!new${capModule}) {
throw new ErrorWithStatus(
'Creation Error',
'Cannot create ${moduleName} right now! Please try again later!',
STATUS_CODES.INTERNAL_SERVER_ERROR,
'CREATE /${moduleName}'
);
}
return new${capModule};
}
/**
* * Get all ${pluralModule} from database.
* @param query Optional query parameters to pass.
* @returns All ${pluralModule} that matched the query as an array.
*/
async getAll${pluralCapModule}FromDB(query?: TQueries<Insert${capModule}>) {
console.log(query);
return await db.select().from(${pluralModule}).orderBy(${pluralModule}.id);
}
/**
* * Retrieve single ${moduleName} from DB.
* @param id ID of ${moduleName} in integer form.
* @returns The matched ${moduleName} against the provided id.
*/
async get${capModule}ByIdFromDB(id: number) {
const ${moduleName}_1 = await db.query.${pluralModule}.findFirst({
where: eq(${pluralModule}.id, id),
});
if (!${moduleName}_1) {
throw new ErrorWithStatus(
'Not Found Error',
\`${capModule} not found with id \${id}!\`,
STATUS_CODES.NOT_FOUND,
'GET ${pluralModule}/:id'
);
}
return ${moduleName}_1;
}
/**
* * Delete single ${moduleName} from DB.
* @param id ID of the ${moduleName} to delete.
* @returns Deleted ${moduleName}'s id as \`{ deleted_id: number }\`
*/
async delete${capModule}ByIdFromDB(id: number) {
const [res] = await db
.delete(${pluralModule})
.where(eq(${pluralModule}.id, id))
.returning({ deleted_id: ${pluralModule}.id });
if (!res) {
throw new ErrorWithStatus(
'Delete Error',
\`Cannot delete ${moduleName} with id \${id}!\`,
STATUS_CODES.NOT_FOUND,
'DELETE ${pluralModule}/:id'
);
}
return res;
}
/**
* * Update ${moduleName} in DB by id.
* @param id ID to find ${moduleName} from DB.
* @param payload Fields to update in ${moduleName}.
*/
async update${capModule}InDB(id: number, payload: Update${capModule}) {
if (!isNotEmptyObject(payload)) {
throw new ErrorWithStatus(
'Empty Payload',
\`Your payload is empty for ${moduleName} with id \${id}!\`,
STATUS_CODES.BAD_REQUEST,
'PATCH ${pluralModule}/:id'
);
}
const [res] = await db.update(${pluralModule}).set(payload).where(eq(${pluralModule}.id, id)).returning();
if (!res) {
throw new ErrorWithStatus(
'Update Error',
\`Cannot update ${moduleName} with id \${id}!\`,
STATUS_CODES.NOT_FOUND,
'PATCH ${pluralModule}/:id'
);
}
return res;
}
}
export const ${moduleName}Services = new ${capModule}Services();
`,
},
// ! module.validation.ts
{
name: `${moduleName}.validation.ts`,
content: `import { ${pluralModule} } from '#/drizzle/schema/${pluralModule}';
import { createInsertSchema } from 'drizzle-zod';
import { z } from 'zod';
const creationSchema = z
.object({})
.strict();
const updateSchema = creationSchema.partial();
/** Convert drizzle table schema to Zod schema */
const drizzleSchema = createInsertSchema(${pluralModule});
export const ${moduleName}Validations = { creationSchema, drizzleSchema, updateSchema };
`,
},
// ! module.types.ts
{
name: `${moduleName}.types.ts`,
content: `import type { ${pluralModule} } from '#/drizzle/schema/${pluralModule}';
export type Insert${capModule} = Omit<typeof ${pluralModule}.$inferInsert, 'id' | 'created_at' | 'updated_at'>;
export type T${capModule} = typeof ${pluralModule}.$inferSelect;
export type Update${capModule} = Partial<Insert${capModule}>;
`,
},
];
}