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 ├── [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 |
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 |
1
2import { Field, Model } from "@akanjs/constant";
3
4@Model.Object("UserObject")
5export class UserObject {
6 @Field.Prop(() => String, { validate: validate.email })
7 email: string;
8
9 @Field.Secret(() => String)
10 password: string | null;
11
12 @Field.Prop(() => [String], { enum: UserRole })
13 roles: string[];
14}
1
2import { Database } from "@akanjs/document";
3
4@Database.Document(() => UserObject)
5export class User extends UserObject {
6 addRole(role: string) {
7 if (!this.roles.includes(role))
8 this.roles = [...this.roles, role];
9 return this;
10 }
11}
1
2import { Service } from "@akanjs/service";
3
4@Service("UserService")
5export class UserService {
6 async createUser(data: UserInput) {
7 const existingUser = await this.findByEmail(data.email);
8 if (existingUser) throw new Error("User exists");
9
10 const user = await this.userModel.createUser(data);
11 return user.set({ roles: ["user"] }).save();
12 }
13}
1
2import { Signal, Mutation } from "@akanjs/signal";
3
4@Signal()
5export class UserSignal {
6 @Mutation.Public()
7 async signin(email: string, password: string) {
8 return this.userService.signin(email, password);
9 }
10}
1
2import { StateCreator } from "zustand";
3
4export const createUserStore: StateCreator<UserStore> = (set) => ({
5 users: [],
6 userForm: { email: "", password: null },
7
8 setUsers: (users) => set({ users }),
9 signin: async () => {
10 const { userForm } = get();
11 await fetch.user.signin(userForm.email, userForm.password);
12 }
13});
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