AppX Core is a production-oriented backend foundation that accelerates the delivery of secure, data-access-controlled APIs with an integrated admin backoffice. It scaffolds a native NestJS application and extends it with opinionated building blocks for authentication, authorization (ABAC-first), CRUD acceleration, and administration through AdminJS.
Built and maintained by AppX (appx-digital.com).
- Why AppX Core
- Key Features
- Architecture Overview
- Getting Started
- Core Concepts
- Permissions
- Generated CRUD Endpoints
- Examples
- Limitations
- FAQ
- Roadmap
- Support
- Security
- Contributing
- License
Modern applications often fail not because teams cannot build endpoints, but because they ship access control too late, build inconsistent admin tooling, and recreate the same authentication and backoffice plumbing across projects.
AppX Core puts security and data-access control first, while keeping development fast:
- Start from a native NestJS structure (no “framework lock-in”).
- Define data in Prisma once.
- Generate a robust baseline for CRUD and service structure.
- Enforce permissions where it matters: at the data layer (ABAC).
- Operate and administrate through a standard AdminJS backoffice.
- NestJS-native scaffold (standard structure, editable modules)
- Prisma ORM integration with opinionated permissions enforcement
- ABAC-first permission filtering (attribute-based access control)
- AdminJS backoffice exposed at
/adminwith configurable access and models - Authentication out-of-the-box
- Cookie/session auth
- JWT auth (recommended)
- Fast CRUD acceleration
- Generated modules, controllers, and services per model
- Standard REST CRUD endpoints
- Developer-friendly extension points
- Add custom endpoints with
@Permission(...) - Use services for business logic, controllers as transport layer
- Add custom endpoints with
AppX Core is designed as a base layer on top of NestJS:
- Your application remains a NestJS project with conventional modules.
- AppX Core provides:
- base configuration and wiring
- authentication modules
- permissions system (ABAC)
- generation pipeline based on Prisma schema
- AdminJS integration
- Node.js 20+
- A running database (MySQL or PostgreSQL tested/validated)
npm install -g @appxdigital/appx-core-cliappx-core create
cd <your-project>Create a .env file at the project root:
## DataBase Configurations ##
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_NAME=generic
DB_PROVIDER=mysql
DB_URL="${DB_PROVIDER}://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
## Project configurations ##
APP_PORT=3000
USE_TRANSACTION=true
## Sessions ##
SESSION_SECRET="change-me"
SESSION_COOKIE_NAME="session_nestjs-project"
SESSION_TTL=86400
## JWT ##
JWT_EXPIRES_IN=10d
JWT_REFRESH_EXPIRES_IN=1y
JWT_SECRET="change-me"
JWT_REFRESH_SECRET="change-me"Edit:
prisma/schema.prisma
Add your application models. You can add fields and relations freely. Avoid removing or changing types of the default models shipped with AppX Core.
If you already have an existing database schema and want to import it:
npx prisma db pullImportant: db pull overrides schema.prisma. Ensure you preserve default AppX Core models/configuration and merge accordingly.
After you edit schema.prisma, run:
appx-core generateRun this every time you change the Prisma schema. This is the only required generation step (no need to run prisma generate separately).
npx prisma migrate devYou will be asked for a migration name.
npm run start:dev- Admin backoffice at
/admin(after you include models insrc/config/admin.config.ts) - Auth endpoints with no prefix:
POST /auth/registerPOST /auth/loginPOST /auth/logoutGET /mePOST /login/jwt(recommended)POST /logout/jwt
AppX Core generates a standard NestJS structure. You will typically see:
src/main.ts– app bootstrap and cross-cutting config (cookies, sessions, logs, CORS)src/app.module.ts– high-level module wiring
Most customization happens in:
src/config/permissions.config.ts– granular ABAC rulessrc/config/admin.config.ts– AdminJS configuration and model registrationprisma/schema.prisma– your data model source of truth
- AdminJS is exposed at
/admin - Models must be included/configured in
admin.config.tsto appear in the UI.
Cookie/session:
POST /auth/registerPOST /auth/loginPOST /auth/logoutGET /me
JWT (recommended):
POST /login/jwtPOST /logout/jwt
AppX Core emphasizes enforcement at the data layer:
- Permissions are designed to be applied automatically during database operations (ABAC), not only at the route layer.
- Implement permissions using:
permissions.config.ts@Permission('actionName')on endpoints- ABAC conditions aligned with Prisma
wherefilters
Unauthenticated requests are treated as a “guest context” (not a role).
Permissions live in src/config/permissions.config.ts:
export const PermissionsConfig = {
ModelName: {
ADMIN: {
findMany: 'ALL',
create: 'ALL',
updateMany: 'ALL',
deleteMany: 'ALL',
},
USER: {
findMany: { conditions: { id: PermissionPlaceholder.USER_ID } },
updateMany: { conditions: { id: PermissionPlaceholder.USER_ID } },
},
},
};'ALL'– unrestricted access{ conditions: { ... } }– ABAC filtering using Prisma-likewheresyntax
Example: allow deletion only if related refresh tokens are expired:
deleteMany: {
conditions: {
refreshTokens: {
every: { expiresAt: { lt: new Date() } },
},
},
},To avoid duplicated config, the permissions engine supports fallback behavior. In practice:
- Define
findManywhen possible, as it generally covers read scenarios. countmay fall back to read permissions when not explicitly defined.
Add custom endpoints and protect them with @Permission(...).
@Permission('exportAuditReport')
@Get('/audit/export')
exportAuditReport() {
return this.service.exportAuditReport();
}Some operations require privileged access (e.g., registration pre-checks). You can temporarily expose a model:
- Endpoint-level (decorator’s second argument), or
- Service-level scoped exposure
Endpoint-level example:
@Permission('checkEmailAvailability', ['user'])
@Get('/auth/email-available')
async isEmailAvailable(@Query('email') email: string) {
return this.service.isEmailAvailable(email);
}Service-level example:
await this.prisma.withExposedModels(['user'], async () => {
// privileged operations here
});Use this sparingly and intentionally. Prefer ABAC whenever feasible.
For each generated model/controller (subject to permissions):
GET /[model_name]GET /[model_name]/:idPOST /[model_name]PUT /[model_name]/:idDELETE /[model_name]/:id
To show a model in the admin backoffice, register it in src/config/admin.config.ts.
Use case: expose a lightweight operational endpoint to verify that background jobs ran recently.
Controller:
@Permission('viewSystemHealth')
@Get('/ops/health')
getHealth() {
return this.service.getHealthSummary();
}Permissions (permissions.config.ts):
export const PermissionsConfig = {
System: {
ADMIN: {
viewSystemHealth: 'ALL',
},
USER: {
// Not defined => denied
},
},
};Service (example logic):
async getHealthSummary() {
return {
status: 'ok',
timestamp: new Date().toISOString(),
};
}Use case: ensure users can only access their own record, even if they try other IDs.
Controller:
@Permission('readMyProfile')
@Get('/me/profile')
getMyProfile() {
return this.service.getMyProfile();
}Permissions:
export const PermissionsConfig = {
User: {
ADMIN: { readMyProfile: 'ALL' },
USER: { readMyProfile: { conditions: { id: PermissionPlaceholder.USER_ID } } },
},
};Service (Prisma query remains straightforward; ABAC filtering is applied by the core):
async getMyProfile() {
return this.prisma.model.user.findFirst({
where: { id: PermissionPlaceholder.USER_ID },
});
}Use case: a public endpoint checks if an email is already used. This must access user data safely, even without a logged-in user.
Controller:
@Permission('checkEmailAvailability', ['user'])
@Get('/auth/email-available')
async isEmailAvailable(@Query('email') email: string) {
return this.service.isEmailAvailable(email);
}Service:
async isEmailAvailable(email: string) {
let exists = false;
await this.prisma.withExposedModels(['user'], async () => {
const user = await this.prisma.model.user.findFirst({ where: { email } });
exists = !!user;
});
return { available: !exists };
}This pattern should be used sparingly and intentionally.
Avoid these Prisma methods because they can bypass or complicate granular permission filtering:
findUnique→ usefindFirstdelete→ usedeleteManywith a single-item filterupdate→ useupdateManywith a single-item filter
Operational guidance:
- Prefer
findMany,findFirst,updateMany, anddeleteMany. - When you intend a single-record operation, use a restrictive
whereclause.
GraphQL is part of the direction, but is not fully ready in the current release. REST CRUD + AdminJS are the primary integration surfaces for now.
Are CRUD endpoints only for AdminJS?
No. They can be used for any API consumer. Permissions are enforced consistently.
Do I need to treat generated modules as read-only?
No. Generated NestJS modules are editable and intended to be extended.
Planned and under consideration:
- Production-ready GraphQL
- Object storage patterns (S3-friendly out-of-the-box support)
- Forgot-password flow
- Email templates + sending primitives
- Push notifications primitives
- WebSockets support
- Stronger testing baseline (unit tests scaffolding and patterns)
- “Agent-ready” development guidance (
agents.txt, conventions for coding agents) - Optional AI-assisted project scaffolding
Email: core@appx-digital.com
Report vulnerabilities privately to core@appx-digital.com. Do not open public issues for sensitive reports.
- Fork the repository.
- Create a feature branch.
- Keep changes focused; add tests where applicable.
- Open a PR with clear motivation and impact.
MIT License. See LICENSE.
