Skip to main content

Quick Start

Before proceeding, make sure to install express, mongoose and reflect-metadata as peer dependencies. These packages are required for the proper functioning of the application and must be installed prior to running the code. You can install them using the npm package manager.

Installation

npm install express mongoose @egose/deco
npm install @types/express reflect-metadata --save-dev

Backend Configuration

Model Router

Using the Router class decorator, you can define a model router class and specify various options and middleware functions with annotations. This approach helps to encapsulate and organize the routing logic for a given model, making the code more modular and easier to understand.

For example:

import {
// class decorator
Router,
// method decorators
DocPermissions,
BaseFilter,
Validate,
Prepare,
Transform,
Decorate,
DecorateAll,
RouteGuard,
// parameter decorators
Request,
Document,
Permissions,
Context,
// property decorator
Option,
} from '@egose/deco';

@Router('User')
export class UserRouter {
@Option() routeGuard = {
list: true,
read: true,
update: true,
delete: 'isAdmin',
create: ['isAdmin', 'dummy'],
subs: {
statusHistory: {
list: true,
read: true,
update: true,
delete: 'isAdmin',
create: 'isAdmin',
},
},
};

@Option() permissionSchema = {
name: {
list: true,
read: true,
update: ['edit.name', 'edit.dummy'],
create: true,
},
role: {
list: 'isAdmin',
read: true,
update: 'edit.role',
create: 'isAdmin',
},
public: {
list: false,
read: true,
update: 'edit.public',
create: true,
},
statusHistory: {
list: (permissions) => {
return false;
},
read: (permissions) => {
return permissions.isAdmin;
},
update: (permissions, modelPermissions) => {
return modelPermissions['edit.statusHistory'];
},
sub: {
name: { list: true, read: true, update: true, create: true },
approved: { list: true, read: true, update: false, create: true },
document: { list: false, read: true, update: true, create: true },
},
},
orgs: { list: true, read: true, update: 'edit.orgs', create: true },
};

@Option() basePath = null;

@Option() identifier = (id) => {
return { name: id };
};

@Option('baseFilter.subs') baseFilterSubs = {
statusHistory: {
list: (permissions) => {
if (permissions.isAdmin) return {};
else return { approved: true };
},
read: (permissions) => {
if (permissions.isAdmin) return {};
else return { approved: true };
},
update: (permissions) => {
if (permissions.isAdmin) return {};
else return false;
},
delete: (permissions) => {
if (permissions.isAdmin) return {};
else return false;
},
},
};

@DocPermissions('default')
docPermissions(@Request() req, @Document() doc, @Permissions() permissions) {
const isMe = String(doc._id) === String(req._user?._id);
const p = {
'edit.name': permissions.isAdmin || isMe,
'edit.role': permissions.isAdmin,
'edit.public': permissions.isAdmin,
'edit.statusHistory': permissions.isAdmin,
'edit.orgs': permissions.isAdmin,
'test:public': doc.public,
};

return p;
}

@BaseFilter('list')
listBaseFilter(@Request() req, @Permissions() permissions) {
if (permissions.isAdmin) return {};
else return { $or: [{ _id: req._user?._id }, { public: true }] };
}

@BaseFilter('read')
readBaseFilter(@Request() req, @Permissions() permissions) {
if (permissions.isAdmin) return {};
else return { _id: req._user?._id };
}

@BaseFilter('update')
updateBaseFilter(@Request() req, @Permissions() permissions) {
if (permissions.isAdmin) return {};
else return { _id: req._user?._id };
}

@BaseFilter('delete')
deleteBaseFilter(@Request() req, @Permissions() permissions) {
if (permissions.isAdmin) return {};
else return { _id: req._user?._id };
}

@Decorate('create')
addCreatedBy(@Document() doc) {
doc._createdBy = 'egose';
return doc;
}
}

Router Module

After defining one or more model routers, you can use the Module class decorator to create an Egose Module that includes these routers. Within the Egose Module, you can also define the Global Permissions function, which provides the user context to each model's middleware functions. This approach helps to organize the routers in a hierarchical structure, making the code more modular and easier to understand. By leveraging the Module decorator, developers can create a centralized location for defining global permissions and integrating them across all of the model routers in their application.

For example:

import mongoose from 'mongoose';
import { RootRouter } from './root.router';
import { UserRouter } from './user.router';
import { OrgRouter } from './org.router';
import { Module, Option, GlobalPermissions, Request } from '@egose/deco';

@Module({
routers: [RootRouter, UserRouter, OrgRouter],
options: { basePath: '/api' },
})
export class RoutersModule {
@Option() permissionField = '_permissions';

@GlobalPermissions()
async globalPermissions(@Request() req) {
const User = mongoose.model('User');
const userName = req.headers.user;

let user;
if (userName) {
user = await User.findOne({ name: userName });
}

req._user = user;
return { isAdmin: user?.role === 'admin' };
}
}

Bootstrapping the Module in an Express Server

Once you have defined the router module, you can bootstrap the API endpoints with the Express server. This involves encapsulating the Express routes within the module definition, which helps to ensure that the routing logic is properly organized and modularized. By leveraging the bootstrap function in the Egose library, developers can easily integrate their router modules with an Express server and start listening for incoming requests.

For example:

import express from 'express';
import { EgoseFactory } from '@egose/deco';

const app = express();
EgoseFactory.bootstrap(RoutersModule, app);