Multiple APIs Recipe
Generate separate Admin and Public APIs from the same Prisma schema with different authentication and suffixes.
Use Case
You want to generate two APIs:
- Admin API - Full CRUD access with admin-only authentication
- Public API - Limited read access for authenticated users
Prerequisites
- Prisma schema configured
- Authentication guards implemented (
JwtAuthGuard,AdminGuard) - TGraph installed
Configuration Files
1. Admin API Configuration
Create tgraph.admin.config.ts:
import type { Config } from '@tgraph/backend-generator';
export const config: Config = {
input: {
schemaPath: 'prisma/schema.prisma',
prismaService: 'src/infrastructure/database/prisma.service.ts',
},
output: {
backend: {
dtos: 'src/dtos/admin',
modules: {
searchPaths: ['src/features', 'src/modules', 'src'],
defaultRoot: 'src/features',
},
staticFiles: {
guards: 'src/guards',
decorators: 'src/decorators',
dtos: 'src/dtos',
interceptors: 'src/interceptors',
utils: 'src/utils',
},
},
dashboard: {
root: 'src/dashboard/src',
resources: 'src/dashboard/src/resources',
},
},
api: {
suffix: 'Admin',
prefix: 'admin-api',
authentication: {
enabled: true,
requireAdmin: true,
guards: [
{ name: 'JwtAuthGuard', importPath: '@/guards/jwt-auth.guard' },
{ name: 'AdminGuard', importPath: '@/guards/admin.guard' },
],
},
},
dashboard: {
enabled: true,
updateDataProvider: true,
components: { form: {}, display: {} },
},
behavior: {
nonInteractive: false,
},
};
2. Public API Configuration
Create tgraph.public.config.ts:
import type { Config } from '@tgraph/backend-generator';
export const config: Config = {
input: {
schemaPath: 'prisma/schema.prisma',
prismaService: 'src/infrastructure/database/prisma.service.ts',
},
output: {
backend: {
dtos: 'src/dtos/public',
modules: {
searchPaths: ['src/features', 'src/modules', 'src'],
defaultRoot: 'src/features',
},
staticFiles: {
guards: 'src/guards',
decorators: 'src/decorators',
dtos: 'src/dtos',
interceptors: 'src/interceptors',
utils: 'src/utils',
},
},
dashboard: {
root: 'src/dashboard/src',
resources: 'src/dashboard/src/resources',
},
},
api: {
suffix: 'Public',
prefix: 'api',
authentication: {
enabled: true,
requireAdmin: false, // Any authenticated user
guards: [{ name: 'JwtAuthGuard', importPath: '@/guards/jwt-auth.guard' }],
},
},
dashboard: {
enabled: false, // No dashboard for public API
updateDataProvider: false,
components: { form: {}, display: {} },
},
behavior: {
nonInteractive: false,
},
};
Generation
Generate both APIs:
# Generate admin API
tgraph api --config tgraph.admin.config.ts
# Generate public API
tgraph api --config tgraph.public.config.ts
# Generate dashboard (using admin config)
tgraph dashboard --config tgraph.admin.config.ts
Generated Structure
your-project/
├── src/
│ ├── features/
│ │ └── user/
│ │ ├── user.admin.controller.ts # Admin API
│ │ ├── user.admin.service.ts
│ │ ├── user.public.controller.ts # Public API
│ │ ├── user.public.service.ts
│ │ └── user.module.ts
│ ├── dtos/
│ │ ├── admin/ # Admin DTOs
│ │ │ └── user-response.admin.dto.ts
│ │ └── public/ # Public DTOs
│ │ └── user-response.public.dto.ts
│ └── app.module.ts
└── tgraph.admin.config.ts
└── tgraph.public.config.ts
API Endpoints
Admin API
// user.admin.controller.ts
@Controller('admin-api/users')
@UseGuards(JwtAuthGuard, AdminGuard)
export class UserAdminController {
// Full CRUD
@Get() list() {}
@Get(':id') getOne() {}
@Post() create() {}
@Put(':id') update() {}
@Delete(':id') delete() {}
}
Routes:
GET /admin-api/users- List all usersGET /admin-api/users/:id- Get user by IDPOST /admin-api/users- Create userPUT /admin-api/users/:id- Update userDELETE /admin-api/users/:id- Delete user
Public API
// user.public.controller.ts
@Controller('api/users')
@UseGuards(JwtAuthGuard)
export class UserPublicController {
// Read-only
@Get() list() {}
@Get(':id') getOne() {}
}
Routes:
GET /api/users- List users (public info only)GET /api/users/:id- Get user by ID (public info only)
Customization
Limit Public API Endpoints
Modify the public service to return only public fields:
// user.public.service.ts
export class UserPublicService {
async findAll(): Promise<UserPublicDto[]> {
const users = await this.prisma.user.findMany({
select: {
id: true,
name: true,
avatar: true,
// Exclude sensitive fields like email, phone
},
});
return users;
}
}
Add Rate Limiting to Public API
Add rate limiting guard to public config:
// tgraph.public.config.ts
api: {
authentication: {
enabled: true,
requireAdmin: false,
guards: [
{ name: 'JwtAuthGuard', importPath: '@/guards/jwt-auth.guard' },
{ name: 'RateLimitGuard', importPath: '@/guards/rate-limit.guard' },
],
},
}
Separate Admin and Public DTOs
Ensure different DTOs for admin and public:
// Admin DTO (includes sensitive fields)
export class UserAdminDto {
id: string;
name: string;
email: string; // Sensitive
phone: string; // Sensitive
role: Role; // Sensitive
createdAt: Date;
}
// Public DTO (only public fields)
export class UserPublicDto {
id: string;
name: string;
avatar: string;
// No sensitive fields
}
NPM Scripts
Add scripts to package.json for easy generation:
{
"scripts": {
"generate:admin": "tgraph api --config tgraph.admin.config.ts",
"generate:public": "tgraph api --config tgraph.public.config.ts",
"generate:dashboard": "tgraph dashboard --config tgraph.admin.config.ts",
"generate:all": "npm run generate:admin && npm run generate:public && npm run generate:dashboard"
}
}
Usage:
npm run generate:all
Testing
Admin API Test
describe('UserAdminController (e2e)', () => {
it('should create user (admin only)', async () => {
const adminToken = getAdminToken();
const response = await request(app.getHttpServer())
.post('/admin-api/users')
.set('Authorization', `Bearer ${adminToken}`)
.send({ name: 'John', email: 'john@example.com' })
.expect(201);
expect(response.body).toHaveProperty('id');
});
it('should reject non-admin users', async () => {
const userToken = getUserToken();
await request(app.getHttpServer())
.post('/admin-api/users')
.set('Authorization', `Bearer ${userToken}`)
.send({ name: 'John', email: 'john@example.com' })
.expect(403);
});
});
Public API Test
describe('UserPublicController (e2e)', () => {
it('should list users (authenticated)', async () => {
const userToken = getUserToken();
const response = await request(app.getHttpServer())
.get('/api/users')
.set('Authorization', `Bearer ${userToken}`)
.expect(200);
expect(Array.isArray(response.body)).toBe(true);
});
it('should not allow write operations', async () => {
const userToken = getUserToken();
await request(app.getHttpServer())
.post('/api/users')
.set('Authorization', `Bearer ${userToken}`)
.send({ name: 'John' })
.expect(404); // Route doesn't exist
});
});
Best Practices
1. Use Consistent Suffixes
Maintain consistent naming across your project:
- Admin:
Adminsuffix,admin-apiprefix - Public:
Publicsuffix,apiprefix
2. Separate DTO Directories
Keep DTOs separate to avoid conflicts:
src/dtos/
├── admin/ # Admin API DTOs
├── public/ # Public API DTOs
└── shared/ # Shared/common DTOs
3. Document API Differences
Add Swagger documentation:
// user.admin.controller.ts
@ApiTags('Admin - Users')
@ApiBearerAuth()
@ApiSecurity('admin-role')
export class UserAdminController {}
// user.public.controller.ts
@ApiTags('Public - Users')
@ApiBearerAuth()
export class UserPublicController {}
4. Version Your APIs
Consider API versioning:
// Admin API
api: {
prefix: 'v1/admin-api',
}
// Public API
api: {
prefix: 'v1/api',
}
Advanced: Three-Tier API
Generate three APIs with different access levels:
Admin API
- Full CRUD
- Admin role required
- All fields accessible
Partner API
- Read + limited write
- Partner role required
- Most fields accessible
Public API
- Read only
- Basic authentication
- Public fields only
Create three config files following the same pattern.
Related
- Configuration Reference - Full config options
- Authentication Guards - Configure guards
- Custom Components - Customize dashboard