@gennext/lb-infra
v0.3.1
Published
Gennex Technology - Loopback 4 Framework
Downloads
139
Maintainers
Readme
@gennext/lb-infra
Gennex Technology - LoopBack 4 infrastructure framework.
@gennext/lb-infra provides shared base classes, components, helpers, datasources, model mixins, repositories and controller generators for building backend services on top of LoopBack 4.
Requirements
- Node.js
>= 18 - Bun
>= 1.3.2 - TypeScript
Install dependencies from the repository root:
bun installPackage Scripts
Run commands from the repository root:
bun run --filter "@gennext/lb-infra" build
bun run --filter "@gennext/lb-infra" clean
bun run --filter "@gennext/lb-infra" rebuild
bun run --filter "@gennext/lb-infra" lint
bun run --filter "@gennext/lb-infra" testRoot shortcut:
bun run rebuild:lb-infraThe build script compiles TypeScript, resolves aliases with tsc-alias, then copies runtime assets into dist:
src/components/authenticate/viewsstatictsconfig.jsonasdist/tsconfig.base.json
CLI
After the package is installed, the lb-infra binary can scaffold the default consumer-app folder structure:
lb-infra initOr target another directory:
lb-infra init ./apps/apiGenerated structure:
src
├── components
├── controllers
├── datasources
├── interceptors
├── middlewares
├── migrations
├── models
├── protos
├── repositories
├── services
└── types
publicThe command creates index.ts placeholders for TypeScript folders and .gitkeep files for asset/proto/migration folders. Existing placeholder files are not overwritten unless --force is passed:
lb-infra init ./apps/api --forceMain Imports
import {
BaseApplication,
DefaultRestApplication,
defineCrudController,
defineKVController,
TzCrudRepository,
SearchableTzCrudRepository,
} from '@gennext/lb-infra';LoopBack re-export aliases:
import {inject, injectable, BindingScope} from '@gennext/lb-infra/lb-core';
import {api, get, post, param, requestBody} from '@gennext/lb-infra/lb-rest';
import {model, property, repository} from '@gennext/lb-infra/lb-repo';
import {authenticate, TokenServiceBindings} from '@gennext/lb-infra/lb-auth';Optional feature exports:
import {SocketIOComponent} from '@gennext/lb-infra/socket-io';
import {GrpcServerComponent} from '@gennext/lb-infra/grpc';Project Layout
src
├── base # base application, sequence, models, repositories, controllers
├── common # constants, environment keys, shared types
├── components # auth, authorize, migration, mail, socket.io, grpc, health check
├── datasources # postgres, redis, memory datasources
├── helpers # logger, queue, redis, network, crypto, storage, testing
├── interceptors # global interceptors
├── middlewares # request middleware
├── migrations # migration helpers
├── mixins # model mixins
└── utilities # shared utility functionsBase Application
Use BaseApplication when you want full control over configuration:
import {BaseApplication} from '@gennext/lb-infra';
import {ApplicationConfig} from '@gennext/lb-infra/lb-core';
export class MyApplication extends BaseApplication {
constructor(options: ApplicationConfig = {}) {
super({
scope: 'MyApplication',
serverOptions: options,
});
}
staticConfigure(): void {
// Bind components, datasources, repositories, services and controllers.
}
getProjectRoot(): string {
return __dirname;
}
validateEnv() {
return {result: true};
}
declareModels(): Set<string> {
return new Set([]);
}
preConfigure(): void {}
postConfigure(): void {}
}Use DefaultRestApplication when you want the standard setup with environment validation, PostgreSQL, in-memory KV datasource, migration component, content-range interceptor and boot options.
Models And Mixins
Common base models:
BaseEntityBaseKVEntityBaseTzEntityBaseUserAuditTzEntityBaseTextSearchTzEntityBaseObjectSearchTzEntityBaseSearchableTzEntityBaseSoftDeleteTzEntity
Common mixins:
IdMixinTzMixinUserAuditMixinSoftDeleteModelMixinSoftPersistentMixinTextSearchMixinObjectSearchMixinVectorMixinDuplicatableMixinDataTypeMixinPrincipalMixin
Example:
import {BaseUserAuditTzEntity} from '@gennext/lb-infra';
import {model, property} from '@gennext/lb-infra/lb-repo';
@model({
settings: {
postgresql: {
schema: 'public',
table: 'user',
},
strict: true,
},
})
export class User extends BaseUserAuditTzEntity {
@property({type: 'string', required: true})
email: string;
}Repositories
Use the base repositories in src/base/repositories:
TzCrudRepositorySearchableTzCrudRepositoryKVRepository
Example:
import {PostgresDataSource, TzCrudRepository} from '@gennext/lb-infra';
import {inject, injectable, BindingScope} from '@gennext/lb-infra/lb-core';
@injectable({scope: BindingScope.SINGLETON})
export class UserRepository extends TzCrudRepository<User> {
constructor(@inject('datasources.postgres') dataSource: PostgresDataSource) {
super(User, dataSource);
}
}Controller Generators
CRUD
defineCrudController creates standard CRUD routes:
GET /POST /PATCH /GET /countGET /find-oneGET /{id}PATCH /{id}PUT /{id}DELETE /{id}
Example:
import {defineCrudController} from '@gennext/lb-infra';
import {api} from '@gennext/lb-infra/lb-rest';
const BaseUserController = defineCrudController<User>({
entity: User,
repository: {name: 'UserRepository'},
controller: {
basePath: '/users',
readonly: false,
},
});
@api({basePath: '/users'})
export class UserController extends BaseUserController {}When extending generated controllers, keep custom method paths relative to the class base path:
@get('/search')
search() {}Key-Value
Use defineKVController for BaseKVEntity models and key-value repositories:
@api({basePath: '/sessions'})
export class SessionController extends defineKVController<Session>({
entity: Session,
repository: {name: 'SessionRepository'},
controller: {
basePath: '/sessions',
readonly: false,
},
}) {}Relations
Use defineRelationViewController or defineRelationCrudController for relation endpoints:
@api({basePath: '/users'})
export class UserPostRelationController extends defineRelationViewController<User, Post>({
entities: {source: User, target: Post},
relation: {name: 'posts', type: EntityRelations.HAS_MANY},
endPoint: 'posts',
}) {}gRPC Usage Example
The gRPC module is exported from @gennext/lb-infra/grpc.
The server flow is:
- Create a
.protofile. - Create a controller method and decorate it with
@grpcMethod. - Register the controller with
app.grpcController(...). - Configure
GrpcServerKeys.GRPC_OPTIONS. - Register
GrpcServerComponent.
Proto File
Example file: src/protos/user.proto
syntax = "proto3";
package demo;
service UserService {
rpc GetUser (GetUserRequest) returns (GetUserResponse);
}
message GetUserRequest {
int32 id = 1;
}
message GetUserResponse {
int32 id = 1;
string email = 2;
string fullName = 3;
}gRPC Controller
import {BaseGrpcController, grpcController, grpcMethod} from '@gennext/lb-infra/grpc';
type GetUserRequest = {
id: number;
};
type GetUserResponse = {
id: number;
email: string;
fullName: string;
};
@grpcController()
export class UserGrpcController extends BaseGrpcController {
constructor() {
super({scope: UserGrpcController.name});
}
@grpcMethod({
proto: 'user.proto',
service: 'demo.UserService',
method: 'GetUser',
})
async getUser(request: GetUserRequest): Promise<GetUserResponse> {
return {
id: request.id,
email: `user-${request.id}@example.com`,
fullName: `User ${request.id}`,
};
}
}service must match the package and service path loaded from the proto definition. In the example above, package demo; service UserService becomes demo.UserService.
method must match the RPC method name in the proto. If method is omitted, the TypeScript method name is used.
Register Server In Application
import {BaseApplication} from '@gennext/lb-infra';
import {
GrpcServerComponent,
GrpcServerKeys,
IGrpcServerOptions,
} from '@gennext/lb-infra/grpc';
import {ServerCredentials} from '@grpc/grpc-js';
import {join} from 'node:path';
import {UserGrpcController} from './controllers/user-grpc.controller';
export class MyApplication extends BaseApplication {
staticConfigure(): void {
this.bind<IGrpcServerOptions>(GrpcServerKeys.GRPC_OPTIONS).to({
identifier: 'my-grpc-server',
protoFolder: join(__dirname, 'protos'),
address: process.env.GRPC_ADDRESS ?? '0.0.0.0:50051',
credentials: ServerCredentials.createInsecure(),
});
this.grpcController(UserGrpcController);
this.component(GrpcServerComponent);
}
getProjectRoot(): string {
return __dirname;
}
validateEnv() {
return {result: true};
}
declareModels(): Set<string> {
return new Set([]);
}
preConfigure(): void {}
postConfigure(): void {}
}GrpcServerComponent starts the gRPC server immediately when the component is registered, except when RUN_MODE=migrate. Register gRPC controllers before registering the component so the server can discover them.
Client Example
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import {initializeGrpcClient} from '@gennext/lb-infra/grpc';
import get from 'lodash/get';
import {join} from 'node:path';
type GetUserResponse = {
id: number;
email: string;
fullName: string;
};
type UserServiceClient = grpc.Client & {
GetUser(
request: {id: number},
callback: (error: grpc.ServiceError | null, response: GetUserResponse) => void,
): void;
};
const protoPath = join(__dirname, 'protos/user.proto');
const packageDefinition = protoLoader.loadSync(protoPath);
const proto = grpc.loadPackageDefinition(packageDefinition);
const UserService = get(proto, 'demo.UserService') as unknown as new (
address: string,
credentials: grpc.ChannelCredentials,
options?: grpc.ClientOptions,
) => UserServiceClient;
const userClient = initializeGrpcClient<UserServiceClient>({
serviceClass: UserService,
address: '127.0.0.1:50051',
credentials: grpc.ChannelCredentials.createInsecure(),
autoConnect: true,
});
userClient.client.GetUser({id: 1}, (error, response) => {
if (error) {
console.error(error);
return;
}
console.log(response);
});Repository-Style Client
For app code that prefers datasource/repository style, use GrpcDataSource and GrpcRepository:
import * as grpc from '@grpc/grpc-js';
import {GrpcDataSource, GrpcRepository} from '@gennext/lb-infra/grpc';
const dataSource = new GrpcDataSource<UserServiceClient>({
dsConfig: {
host: '127.0.0.1',
port: 50051,
credentials: grpc.ChannelCredentials.createInsecure(),
serviceClassResolver: () => UserService,
},
});
class UserGrpcRepository extends GrpcRepository<UserServiceClient> {
constructor() {
super({dataSource, scope: UserGrpcRepository.name});
}
getUser(id: number): Promise<GetUserResponse> {
return new Promise((resolve, reject) => {
this.getServiceClient().GetUser({id}, (error, response) => {
if (error) {
reject(error);
return;
}
resolve(response);
});
});
}
}The current helper examples above use standard unary request/response calls.
Components
Available components include:
AuthenticateComponent: JWT/basic/oauth2 strategies, auth middleware, token services and auth controller generator.AuthorizeComponent: Casbin authorization, role/permission models, adapters, provider and interceptor.MigrationComponent: migration model, repository and runtime wiring.HealthCheckComponent: application and datasource health checks.StaticAssetComponent: MinIO/static asset controllers.MailComponent: mail service, template engine, transports and queue executors.SocketIOComponent: Socket.IO server/client helpers and Redis adapter support.GrpcServerComponent: gRPC server support.CrashReportComponent: crash report providers and services.
Helpers
Important helper groups:
- Logger:
LoggerFactory,ApplicationLogger - Redis:
RedisHelper,RedisClusterHelper - Queue:
QueueHelper,BullMQHelper,MQTTClientHelper - Network: HTTP request, TCP, TLS TCP and UDP helpers
- Crypto:
AES,RSA - Storage:
MinioHelper,DIContainerHelper - Worker thread helpers
- Testing helpers
CronHelper
Datasources
Built-in datasources:
PostgresDataSourceRedisDataSourceKvMemDataSource
Common environment keys are defined in src/common/environments.ts, including server, PostgreSQL and datasource settings.
Development Checklist
Before opening a change:
bun run --filter "@gennext/lb-infra" build
bun run --filter "@gennext/lb-infra" lint
bun run --filter "@gennext/lb-infra" testWhen adding or changing exports:
- Update
src/index.tsor the relevant moduleindex.ts. - Update
package.jsonexportswhen adding a new public subpath. - Build the package.
- Verify the consumer app can import the new path.
Notes For Consumer Apps
- Prefer the package aliases
lb-core,lb-rest,lb-repoandlb-authto keep LoopBack dependency versions consistent. - Add
@api({basePath: ...})on the final exported class when using generated controllers. - Bind required component options before starting the application, especially authentication token options and auth service bindings.
- Ensure build output includes copied runtime assets when publishing or packaging.
