Skip to content

Services

Services in GamanJS hold your business logic. Created with composeService, services are independent from the HTTP layer — pure logic.

The recommended pattern is to export the service instance as a constant and export its return type for easier dependency injection in controllers.

src/modules/app/services/AppService.ts
import { composeService } from 'gaman/compose';
import type { RT } from 'gaman/types';
export const AppService = composeService(() => {
/**
* You can put your private logic, state, or database connections here
*/
return {
WelcomeMessage() {
return '❤️ Welcome to GamanJS';
},
};
});
// Export the return type of the service
export type AppService = RT<typeof AppService>;
src/modules/app/services/UserService.ts
import { composeService } from 'gaman/compose';
import type { RT } from 'gaman/types';
interface User {
id: number;
name: string;
email: string;
}
const users: User[] = [
{ id: 1, name: 'Angga', email: 'angga@example.com' },
];
export const UserService = composeService(() => {
return {
findAll(): User[] {
return users;
},
findById(id: number): User | undefined {
return users.find((u) => u.id === id);
},
create(data: Omit<User, 'id'>): User {
const newUser = { ...data, id: users.length + 1 };
users.push(newUser);
return newUser;
},
};
});
export type UserService = RT<typeof UserService>;

Services can also receive dependencies from other services:

src/modules/app/services/AuthService.ts
import { composeService } from 'gaman/compose';
import type { RT } from 'gaman/types';
import { EmailService } from './EmailService';
export const AuthService = composeService(
(emailService: RT<typeof EmailService> = EmailService()) => {
return {
register(name: string, email: string) {
// ... register logic
emailService.sendWelcome(email);
return { name, email };
},
};
}
);
export type AuthService = RT<typeof AuthService>;

When using a service in a controller, use the recommended destructuring pattern with a Deps type.

import { composeController } from 'gaman/compose';
import { UserService } from '../services/UserService';
export type Deps = {
userService: UserService;
};
export default composeController(({ userService }: Deps) => ({
GetAll(ctx) {
const users = userService.findAll();
return ctx.send(users).ok();
},
}));
  • Services do not have access to ctx — this is intentional so logic stays independent from HTTP.
  • One service can be used by multiple controllers.
  • Services can depend on each other.
  • Use export type Name = RT<typeof Name> to make DI clean and type-safe.
  • Separation of concerns: Service = business logic, Controller = HTTP handling.