generic structure

This commit is contained in:
Julian Krauser 2025-01-17 14:07:39 +01:00
parent 6133c0a39f
commit d28e029e33
16 changed files with 3716 additions and 0 deletions

1
.gitignore vendored
View file

@ -130,3 +130,4 @@ dist
.yarn/install-state.gz
.pnp.*
db.sqlite

5
.prettierrc Normal file
View file

@ -0,0 +1,5 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"printWidth": 120
}

3478
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

28
package.json Normal file
View file

@ -0,0 +1,28 @@
{
"name": "express-template-structure",
"version": "1.0.0",
"main": "dist/index.js",
"scripts": {
"typeorm": "typeorm-ts-node-commonjs",
"build": "tsc",
"start": "node .",
"dev": "npm run build && set NODE_ENV=development && npm run start"
},
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"cors": "^2.8.5",
"express": "^5.0.1",
"reflect-metadata": "^0.2.2",
"sqlite3": "^5.1.7",
"typeorm": "^0.3.20"
},
"devDependencies": {
"@types/cors": "^2.8.14",
"@types/express": "^4.17.17",
"@types/node": "^16.18.41",
"ts-node": "10.7.0",
"typescript": "^4.5.2"
}
}

View file

@ -0,0 +1,34 @@
import { Request, Response } from "express";
import GenericService from "../services/GenericService";
import BaseEntity from "../entities/baseEntity";
export default class GenericController<Service extends GenericService<Repository>, Repository extends BaseEntity> {
constructor(protected readonly service: Service) {}
async getAll(req: Request, res: Response): Promise<void> {
let data: Array<Repository> = await this.service.getAll();
res.json(data);
}
async getById(req: Request, res: Response): Promise<void> {
let id: number = parseInt(req.params.id);
let data: Repository = await this.service.getById(id);
res.json(data);
}
async create(req: Request, res: Response): Promise<void> {
let insert = await this.service.insert(req.body);
res.json(insert.identifiers);
}
async update(req: Request, res: Response): Promise<void> {
let update = await this.service.insert(req.body);
res.json(update.identifiers);
}
async delete(req: Request, res: Response): Promise<void> {
let id: number = parseInt(req.params.id);
await this.service.delete(id);
res.sendStatus(204);
}
}

View file

@ -0,0 +1,21 @@
import { Request, Response } from "express";
import User from "../entities/user";
import UserService from "../services/userService";
import GenericController from "./GenericController";
export default class UserController extends GenericController<UserService, User> {
constructor() {
super(new UserService());
}
// overwrite defined generic controller function
async getById(req: Request, res: Response): Promise<void> {
res.send("overwritten getById");
}
// extend generic controller by specific function
async getUserByFirstname(req: Request, res: Response): Promise<void> {
let data: Array<User> = await this.service.getUsersByFirstname(req.params.firstname);
res.json(data);
}
}

14
src/data-source.ts Normal file
View file

@ -0,0 +1,14 @@
import "dotenv/config";
import "reflect-metadata";
import { DataSource } from "typeorm";
import User from "./entities/user";
const dataSource = new DataSource({
type: "sqlite",
database: "db.sqlite",
synchronize: true,
logging: true,
entities: [User],
});
export { dataSource };

View file

@ -0,0 +1,6 @@
import { Entity, PrimaryGeneratedColumn } from "typeorm";
export default abstract class BaseEntity {
@PrimaryGeneratedColumn("increment")
id: number;
}

8
src/entities/user.ts Normal file
View file

@ -0,0 +1,8 @@
import { Column, Entity } from "typeorm";
import BaseEntity from "./baseEntity";
@Entity()
export default class User extends BaseEntity {
@Column({ type: "varchar", length: 255 })
firstname: string;
}

12
src/index.ts Normal file
View file

@ -0,0 +1,12 @@
import "dotenv/config";
import express from "express";
import { dataSource } from "./data-source";
dataSource.initialize();
const app = express();
import router from "./routes/index";
router(app);
app.listen(5000, () => {
console.log(`listening on *:5000`);
});

View file

@ -0,0 +1,16 @@
import express from "express";
import GenericController from "../controller/GenericController";
import GenericService from "../services/GenericService";
import BaseEntity from "../entities/baseEntity";
export default class GenericRouter<
Controller extends GenericController<Service, Repository>,
Service extends GenericService<Repository>,
Repository extends BaseEntity
> {
public router = express.Router({ mergeParams: true });
constructor(protected controller: Controller) {
this.router.get("/", (req, res) => this.controller.getAll(req, res));
this.router.get("/:id", (req, res) => this.controller.getById(req, res));
}
}

18
src/routes/index.ts Normal file
View file

@ -0,0 +1,18 @@
import express from "express";
import type { Express } from "express";
import cors from "cors";
import UserRouter from "./userRouter";
export default (app: Express) => {
app.set("query parser", "extended");
app.use(express.json());
app.use(
express.urlencoded({
extended: true,
})
);
app.use(cors());
app.options("*path", cors());
app.use("/user", new UserRouter().router);
};

12
src/routes/userRouter.ts Normal file
View file

@ -0,0 +1,12 @@
import UserController from "../controller/userController";
import User from "../entities/user";
import UserService from "../services/userService";
import GenericRouter from "./GenericRouter";
export default class UserRouter extends GenericRouter<UserController, UserService, User> {
constructor() {
super(new UserController());
// extend generic routes by specific routes
this.router.get("/name/:firstname", (req, res) => this.controller.getUserByFirstname(req, res));
}
}

View file

@ -0,0 +1,30 @@
import { DeleteResult, EntityTarget, InsertResult, UpdateResult } from "typeorm";
import { dataSource } from "../data-source";
import BaseEntity from "../entities/baseEntity";
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
export default class GenericService<Repository extends BaseEntity> {
constructor(protected readonly entity: EntityTarget<Repository>) {}
async getAll(): Promise<Repository[]> {
return await dataSource.getRepository(this.entity).createQueryBuilder().getMany();
}
async getById(rowId: number): Promise<Repository> {
return await dataSource.getRepository(this.entity).createQueryBuilder().where({ id: rowId }).getOneOrFail();
}
async insert(
value: QueryDeepPartialEntity<Repository> | QueryDeepPartialEntity<Repository>[]
): Promise<InsertResult> {
return await dataSource.createQueryBuilder().insert().into(this.entity).values(value).execute();
}
async update(value: QueryDeepPartialEntity<Repository>): Promise<UpdateResult> {
return await dataSource.createQueryBuilder().update(this.entity).set(value).execute();
}
async delete(rowId: number): Promise<DeleteResult> {
return await dataSource.createQueryBuilder().delete().from(this.entity).where({ id: rowId }).execute();
}
}

View file

@ -0,0 +1,14 @@
import { dataSource } from "../data-source";
import User from "../entities/user";
import GenericService from "./GenericService";
export default class UserService extends GenericService<User> {
constructor() {
super(User);
}
// extend generic service by specific function
async getUsersByFirstname(firstname: string): Promise<User[]> {
return await dataSource.getRepository(User).createQueryBuilder().where({ firstname }).getMany();
}
}

19
tsconfig.json Normal file
View file

@ -0,0 +1,19 @@
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"esModuleInterop": true,
"target": "esnext",
"noImplicitAny": true,
"skipLibCheck": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": ["node_modules/*"]
}
},
"include": ["src/**/*"]
}