Skip to content

Instantly share code, notes, and snippets.

@marcj
Created July 15, 2021 18:53
Show Gist options
  • Save marcj/4ea2a6f45888b637a6ad72cc8ab41d84 to your computer and use it in GitHub Desktop.
Save marcj/4ea2a6f45888b637a6ad72cc8ab41d84 to your computer and use it in GitHub Desktop.
Deepkit REST crud automatic controller
#!/usr/bin/env ts-node-script
import 'reflect-metadata';
import { Application } from '@deepkit/framework';
import { http } from '@deepkit/http';
import { ClassSchema, entity, getClassSchema, t } from '@deepkit/type';
import { Database } from '@deepkit/orm';
import { MongoDatabaseAdapter } from '@deepkit/mongo';
import { ClassType, getObjectKeysSize } from '@deepkit/core';
import { AppModule } from '@deepkit/app';
export const emailValidation = /^\S+@\S+$/;
@entity.name('group')
class Group {
@t.autoIncrement.primary public id: number = 0;
constructor(
@t public name: string,
) {
}
}
@entity.name('user')
class User {
@t.autoIncrement.primary public id: number = 0;
@t.pattern(emailValidation) email?: string;
@t.reference() group?: Group;
constructor(
@t public username: string,
) {
}
}
class MainDatabase extends Database {
constructor() {
super(new MongoDatabaseAdapter('mongodb://localhost/dynamic'), [User, Group]);
}
}
function createController(schema: ClassSchema): ClassType {
if (!schema.name) throw new Error(`Class ${schema.getClassName()} needs an entity name via @entity.name()`);
const primaryKey = schema.getPrimaryField();
class ListQuery {
@t.partial(schema) filter?: Partial<any>;
@t select?: string;
@t.map(t.union('asc', 'desc')) orderBy: { [name: string]: 'asc' | 'desc' } = {};
@t.map(t.string) joins: { [name: string]: string } = {};
@t offset: number = 0;
@t limit: number = 0;
}
@http.controller('/entity/' + schema.name)
class RestController {
constructor(protected database: MainDatabase) {
}
@http.GET('')
async list(@http.queries() options: ListQuery) {
options.limit = Math.min(100, options.limit); //max 100
let query = this.database.query(schema);
for (const field of Object.keys(query.orderBy)) {
if (!schema.hasProperty(field)) throw new Error(`Field ${field} does not exist`);
}
for (const [field, projection] of Object.entries(options.joins)) {
if (!schema.hasProperty(field)) throw new Error(`Join ${field} does not exist`);
let join = query.useJoinWith(field);
if (projection.length) {
join = join.select(...projection.split(','));
}
query = join.end();
}
if (options.select) query = query.select(...options.select.split(','));
if (getObjectKeysSize(query.orderBy) > 0) query.model.sort = query.orderBy;
return await query
.filter(options.filter)
.limit(options.limit)
.skip(options.offset)
.find();
}
@http.POST('').description('Adds a new ' + schema.name)
async post(@t.type(schema) @http.body() body: any) {
//body is automatically validated
await this.database.persist(body);
return { [primaryKey.name]: body[primaryKey.name] };
}
@http.DELETE(':id').description('Delete a ' + schema.name)
async remove(id: number) {
const result = await this.database.query(schema).filter({ [primaryKey.name]: id }).deleteOne();
return {deleted: result.modified};
}
@http.PUT(':id').description('Updates ' + schema.name)
async put(id: number, @t.type(schema) @http.body() body: any) {
const item = await this.database.query(schema).filter({ [primaryKey.name]: id }).findOne();
delete body[primaryKey.name]; //we dont allow to change primary
Object.assign(item, body);
await this.database.persist(item);
return true;
}
}
Object.defineProperty(RestController, 'name', { value: 'RestController' + schema.getClassName() });
return RestController;
}
function createDynamicRoutes(schemas: ClassType[]) {
const controllers = schemas.map(getClassSchema).map(createController);
return new AppModule({
controllers: controllers
}
);
}
Application.create({
providers: [MainDatabase],
imports: [createDynamicRoutes([User, Group])]
}).run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment