Encapsulate all domain-specific logic in a consistent structure
Seamless integration between MongoDB, NestJS server, and React client
Ensure type safety across entire stack with auto-generated types
Provide automatic CRUD operations through standardized patterns
libs/shared/lib/[module-name]/ ├── [ModuleName].Template.tsx // Form components ├── [ModuleName].Unit.tsx // Card/list view components ├── [ModuleName].Util.tsx // Utility components ├── [ModuleName].View.tsx // Single item view components ├── [ModuleName].Zone.tsx // Main zone layout ├── _server.ts // Module registration ├── [module-name].constant.ts // Model definitions ├── [module-name].dictionary.ts // Translations ├── [module-name].document.ts // Database schema ├── [module-name].service.ts // Business logic ├── [module-name].signal.ts // API endpoints ├── [module-name].store.ts // Client state └── index.tsx // Module exports
Component | Purpose |
---|---|
Template | Form components for creating/editing data |
Unit | Card/list item components for summarized data |
Util | Specialized utility components (dashboard, insights) |
View | Detailed view components for full data models |
Zone | Layout containers that organize multiple components |
_server.ts | Module registration file |
constant.ts | Model definitions and types |
dictionary.ts | Internationalization translations |
document.ts | MongoDB schema definitions |
service.ts | Business logic implementation |
signal.ts | API endpoint definitions |
store.ts | Client-side state management |
index.tsx | Module exports |
import { Field, Model } from "@akanjs/constant";
@Model.Object("UserObject")
export class UserObject {
@Field.Prop(() => String, { validate: validate.email })
email: string;
@Field.Secret(() => String)
password: string | null;
@Field.Prop(() => [String], { enum: UserRole })
roles: string[];
}
import { Database } from "@akanjs/document";
@Database.Document(() => UserObject)
export class User extends UserObject {
addRole(role: string) {
if (!this.roles.includes(role))
this.roles = [...this.roles, role];
return this;
}
}
import { Service } from "@akanjs/service";
@Service("UserService")
export class UserService {
async createUser(data: UserInput) {
const existingUser = await this.findByEmail(data.email);
if (existingUser) throw new Error("User exists");
const user = await this.userModel.createUser(data);
return user.set({ roles: ["user"] }).save();
}
}
import { Signal, Mutation } from "@akanjs/signal";
@Signal()
export class UserSignal {
@Mutation.Public()
async signin(email: string, password: string) {
return this.userService.signin(email, password);
}
}
import { StateCreator } from "zustand";
export const createUserStore: StateCreator<UserStore> = (set) => ({
users: [],
userForm: { email: "", password: null },
setUsers: (users) => set({ users }),
signin: async () => {
const { userForm } = get();
await fetch.user.signin(userForm.email, userForm.password);
}
});
Use PascalCase for classes/components, camelCase for files
Use @Field.Secret for sensitive data, apply permission guards
Keep business logic in services, use signals only for API
Use dataloader pattern, create proper indexes
Create signal tests for endpoints, mock services for unit tests
Separate Template/Unit/View/Zone components
Use GraphQL subscriptions and WebSockets