API framework with authentication built-in.
database/schema.dbml β ./ace api β Complete API with Auth
Every project starts with user registration, login, roles, 2FA, and token management. No setup required.
β Ready-to-use Authentication
- User registration (member/admin roles)
- Login with token (access + refresh)
- 2FA (Google Authenticator compatible)
- Password hashing, token management
- Login history logging
β Auto-Generated from Schema
- Complete CRUD APIs
- Relationship endpoints
- Database migrations
- Input validation structure
β Zero Configuration
- Auto-routing (method names β URLs)
- JSON responses
- Middleware support
git clone <this-repo>
cp .env.example .env
chmod +x ace.phpEdit .env: Set database credentials and generate APP_KEY
# Generate APP_KEY
openssl rand -base64 32
# Copy output to .env: APP_KEY=<your-generated-key>./ace api # Generate API from schema
./ace migrate # Create database tables
./ace serve # Start server on :8080Your API is now running with complete authentication. Test it:
Register a user:
curl -X POST http://localhost:8080/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "password123",
"name": "John Doe",
"nickname": "johndoe"
}'Login:
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "password123"
}'Response:
{
"access_token": "eyJ1c2VyX2lkIjoxLCJ1...",
"refresh_token": "eyJ1c2VyX2lkIjoxLCJ1...",
"expires_in": 3600,
"token_type": "Bearer",
"user": {
"id": 1,
"email": "[email protected]",
"user_type": "member"
}
}Use the API:
curl http://localhost:8080/api/auth/me \
-H "Authorization: Bearer eyJ1c2VyX2lkIjoxLCJ1..."Done. You have a working API with authentication in 2 minutes.
These are available immediately without writing any code:
POST /api/auth/register Register new user
POST /api/auth/login Login and get tokens
POST /api/auth/logout Logout (revoke token)
POST /api/auth/refresh Refresh access token
GET /api/auth/me Get current user info
POST /api/auth/enable2fa Enable 2FA (get QR code)
POST /api/auth/disable2fa Disable 2FA
POST /api/auth/verify2fa Verify 2FA code
GET /api/users List users
GET /api/users/show/{id} Get user details
PUT /api/users/update/{id} Update user
DELETE /api/users/destroy/{id} Delete user
GET /api/members List member profiles
POST /api/members/store Create member profile
PUT /api/members/update/{id} Update member profile
GET /api/admins List admin profiles
POST /api/admins/store Create admin profile
GET /api/login-logs Get login history
ACE uses a split authentication design:
βββββββββββββββ
β users β Login credentials & type
β (auth) β - email, password
ββββββββ¬βββββββ - user_type (member/admin)
β - status (active/inactive/suspended)
β
βββββ΄βββββ¬βββββββββββββββ
β β β
ββββΌββββ ββββΌβββββ ββββββββΌβββββββ
βmemberβ β admin β β tokens β
β(info)β β(info) β β (sessions) β
ββββββββ βββββββββ βββββββββββββββ
Why this design?
- One login system (
userstable) - Multiple user types with different data needs
- Clean separation: auth vs user data
- Easy to extend (add more roles)
Example flow:
- Register β Creates
usersrow +membersoradminsrow - Login β Validates
userscredentials, createstokensrow - API call β Validates token, loads user + profile data
curl -X POST http://localhost:8080/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "password123",
"name": "John Doe",
"nickname": "johndoe",
"phone": "010-1234-5678",
"bio": "Hello world"
}'Creates:
usersrow: email, password, user_type='member'membersrow: name, nickname, phone, bio, etc.
curl -X POST http://localhost:8080/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "admin123",
"name": "Admin User",
"user_type": "admin",
"role": "super_admin",
"permissions": "all"
}'Creates:
usersrow: email, password, user_type='admin'adminsrow: name, role, permissions
1. Request 2FA setup:
curl -X POST http://localhost:8080/api/auth/enable2fa \
-H "Authorization: Bearer <your_access_token>"2. Response:
{
"message": "2FA enabled successfully",
"qr_code_url": "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=...",
"backup_codes": [
"12345678",
"87654321",
"11223344",
...
],
"instructions": "Scan the QR code with Google Authenticator or Authy app"
}3. Scan QR code with Google Authenticator, Authy, or any TOTP app
4. Login with 2FA:
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "password123",
"two_factor_code": "123456"
}'Backup codes: Save them! Use if you lose your phone.
Every login attempt is logged in login_logs table:
- User ID
- IP address
- User agent (browser/device)
- Success/failure
- Failure reason
- Timestamp
View login history:
curl http://localhost:8080/api/login-logs \
-H "Authorization: Bearer <admin_token>"Use cases:
- Detect suspicious activity
- Track failed login attempts
- Monitor account security
- Compliance/audit requirements
Login β Server generates 2 tokens:
access_token- Short-lived (1 hour), for API callsrefresh_token- Long-lived (30 days), to get new access token
Access token expires β Use refresh token to get new one:
curl -X POST http://localhost:8080/api/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "<your_refresh_token>"
}'Logout β Revokes tokens:
curl -X POST http://localhost:8080/api/auth/logout \
-H "Authorization: Bearer <your_access_token>"Token storage:
- Stored in
tokenstable - Includes: user_id, token, type (access/refresh), expires_at
- Validates on every API call
- Expired tokens automatically rejected
The included database/schema.dbml provides a complete authentication system:
users - Login credentials
- email, password (bcrypt hashed)
- user_type (member, admin, or custom)
- status (active, inactive, suspended)
- email_verified_at
- created_at, updated_at
members - Member profiles
- user_id (β users.id)
- name, nickname (unique)
- phone, avatar_url, bio
- birth_date
- created_at, updated_at
admins - Admin profiles
- user_id (β users.id)
- name, role (admin, super_admin, etc.)
- permissions (JSON)
- last_login_at
- created_at, updated_at
tokens - Session management
- user_id (β users.id)
- token (unique)
- type (access, refresh)
- expires_at
- created_at
login_logs - Security audit
- user_id (β users.id)
- email, ip_address, user_agent
- success (boolean)
- failure_reason
- created_at
two_factor_auth - 2FA settings
- user_id (β users.id)
- secret (TOTP secret)
- is_enabled (boolean)
- backup_codes (JSON array)
- last_used_at
- created_at, updated_at
posts, categories, comments - Blog/forum example
You can remove these and add your own tables.
Edit database/schema.dbml and run ./ace api to regenerate.
Table products {
id int [pk, increment, note: 'auto:db']
user_id int [ref: > users.id, note: 'auto:server:from=auth']
name varchar(255) [not null, note: 'input:required']
price decimal(10,2) [note: 'input:required|min:0']
category_id int [ref: > categories.id, note: 'input:required']
status enum('draft', 'published') [default: 'draft', note: 'input:optional']
created_at timestamp [note: 'auto:db']
updated_at timestamp [note: 'auto:db']
}
Table categories {
id int [pk, increment, note: 'auto:db']
name varchar(255) [not null, note: 'input:required']
slug varchar(255) [unique, note: 'auto:server:from=name']
created_at timestamp [note: 'auto:db']
}
Run ./ace api β Automatically generates:
- Migrations
- Models with relationships
- Services (CRUD + business logic hooks)
- Controllers (REST endpoints)
Input Fields (from API requests):
name varchar(255) [note: 'input:required'] # Required
email varchar(255) [note: 'input:required|email'] # With validation
bio text [note: 'input:optional'] # Optional
Auto-Generated (Database):
id int [pk, increment, note: 'auto:db']
created_at timestamp [note: 'auto:db']
updated_at timestamp [note: 'auto:db']
Auto-Generated (Server):
slug varchar(255) [note: 'auto:server:from=name'] # From name field
user_id int [note: 'auto:server:from=auth'] # From logged-in user
order_number varchar(50) [note: 'auto:server:uuid'] # UUID
deleted_at timestamp [note: 'auto:server:soft_delete'] # Soft delete
π Complete DBML Guide: For complex scenarios (5+ table joins, conditional logic, e-commerce examples), see docs/DBML_GUIDE.md
π¦ Ready-to-Use Examples:
examples/ecommerce-schema.dbml- Full e-commerce system (orders, payments, shipping, reviews)examples/OrderService_example.php- Complex business logic with transactionsexamples/OrderController_example.php- Custom API endpoints
Edit app/Http/Kernel.php:
public array $middlewareGroups = [
'api' => [
\APP\Http\Middleware\AuthMiddleware::class,
],
];Now all /api/* routes require authentication.
public array $middlewareGroups = [
'admin' => [
\APP\Http\Middleware\AuthMiddleware::class,
],
];Routes on admin.yourdomain.com require auth, but api.yourdomain.com doesn't.
Don't add middleware to routes you want public:
public array $middlewareGroups = [
'api' => [], // No middleware = public
];Or create a custom PublicController and don't apply middleware to it.
Generated services have hooks for your code:
// app/Services/ProductService.php
class ProductService
{
// ========================================
// Auto-generated CRUD (don't modify)
// ========================================
public function getAll(array $filters = []): array { /* ... */ }
public function findById(int $id): ?array { /* ... */ }
public function create(array $data): array { /* ... */ }
public function update(int $id, array $data): int { /* ... */ }
public function delete(int $id): int { /* ... */ }
// ========================================
// Your custom business logic below
// ========================================
public function getFeaturedProducts(): array {
return Product::where('is_featured', 1);
}
public function applyDiscount(int $id, float $percent): void {
$product = Product::find($id);
$newPrice = $product['price'] * (1 - $percent / 100);
Product::update($id, ['price' => $newPrice]);
}
public function getByCategory(int $categoryId): array {
return Product::where('category_id', $categoryId);
}
}Add corresponding controller methods:
// app/Http/Controllers/ProductController.php
public function getFeatured(): array {
return $this->productService->getFeaturedProducts();
}
public function postApplyDiscount(int $id): array {
$data = $this->request->getParsedBody();
$this->productService->applyDiscount($id, $data['percent']);
return ['message' => 'Discount applied'];
}New endpoints:
GET /api/products/featuredPOST /api/products/apply-discount/{id}
ACE is designed for AI-assisted development. AI agents can safely extend your application following clear patterns.
β AI agents can:
- Edit
database/schema.dbml(define new tables) - Add custom logic in
app/Services/(business logic) - Add custom endpoints in
app/Http/Controllers/(API endpoints) - Modify
app/Models/(add helper methods)
β AI agents cannot:
- Modify
ace/directory (framework core is read-only) - Change framework behavior
- Alter auto-generated CRUD code
Keep DBML simple with only 3 annotation types:
// 1. User input
name varchar(255) [note: 'input:required']
// 2. Database auto-generated
id int [pk, increment, note: 'auto:db']
created_at timestamp [note: 'auto:db']
// 3. Server auto-generated
user_id int [note: 'auto:server:from=auth']
slug varchar(255) [note: 'auto:server:from=title']
For complex workflows (10+ tables), use BaseService:
// app/Services/OrderService.php
class OrderService extends BaseService
{
public function createFromCart(array $data): array
{
// Validate input
$this->validate($data, [
'user_id' => 'required',
'address' => 'required',
]);
// Use transaction for multi-table operations
return $this->transaction(function() use ($data) {
// 1. Create order
$orderId = Order::create([...]);
// 2. Create order items
foreach ($data['items'] as $item) {
OrderItem::create([...]);
}
// 3. Update stock
$this->updateStock($data['items']);
return Order::find($orderId);
});
}
}AI-generated code must be simple and verifiable:
- Max 100 lines per function (break into smaller helpers)
- Max 3 nesting levels (use early returns)
- Clear comments (explain business logic)
π AI Agent Guide - Comprehensive rules, patterns, and examples for AI development
π¦ Example Code:
examples/OrderService_example.php- Multi-table operationsexamples/OrderController_example.php- Custom endpointsexamples/ecommerce-schema.dbml- Complete schema
Controller method names automatically map to URLs:
class ProductController {
// Standard CRUD
public function getIndex() {} // GET /api/product
public function postStore() {} // POST /api/product/store
public function getShow(int $id) {} // GET /api/product/show/{id}
public function putUpdate(int $id) {} // PUT /api/product/update/{id}
public function deleteDestroy(int $id) {} // DELETE /api/product/destroy/{id}
// Custom endpoints
public function getFeatured() {} // GET /api/product/featured
public function postSearch() {} // POST /api/product/search
public function getByCategory(int $id) {} // GET /api/product/by-category/{id}
}Pattern: {httpMethod}{ActionName} β {METHOD} /api/{resource}/{action}
All models inherit these methods:
// Read
Product::getAll(); // Get all records
Product::find($id); // Find by ID
Product::where('status', 'active'); // Find by column
// Write
Product::create($data); // Insert (auto-filters to fillable)
Product::update($id, $data); // Update
Product::delete($id); // Delete
// Raw SQL
Product::query($sql, $bindings); // Custom query
Product::execute($sql, $bindings); // Custom statement./ace api # Generate API from database/schema.dbml
./ace migrate # Run database migrations
./ace serve # Start development server (localhost:8080)- Always generate APP_KEY:
openssl rand -base64 32(don't use default!) - Use HTTPS in production (tokens over HTTP = insecure)
- Enable 2FA for admin accounts (mandatory for sensitive operations)
- Monitor
login_logstable (detect brute force, suspicious IPs) - Set appropriate token expiration (balance security vs UX)
- Validate input (use DBML validation rules)
- Rate limit auth endpoints (prevent brute force)
- Rotate tokens on password change (invalidate all existing tokens)
Goal: Build a product catalog with user reviews.
1. Modify database/schema.dbml:
// Keep default auth tables (users, members, admins, tokens, login_logs, two_factor_auth)
// Add your tables
Table products {
id int [pk, increment, note: 'auto:db']
user_id int [ref: > users.id, note: 'auto:server:from=auth']
name varchar(255) [not null, note: 'input:required']
slug varchar(255) [unique, note: 'auto:server:from=name']
price decimal(10,2) [note: 'input:required|min:0']
stock int [default: 0, note: 'input:optional']
category_id int [ref: > categories.id, note: 'input:required']
created_at timestamp [note: 'auto:db']
}
Table categories {
id int [pk, increment, note: 'auto:db']
name varchar(255) [not null, note: 'input:required']
slug varchar(255) [unique, note: 'auto:server:from=name']
created_at timestamp [note: 'auto:db']
}
Table reviews {
id int [pk, increment, note: 'auto:db']
product_id int [ref: > products.id, note: 'input:required']
user_id int [ref: > users.id, note: 'auto:server:from=auth']
rating int [note: 'input:required|min:1|max:5']
comment text [note: 'input:optional']
created_at timestamp [note: 'auto:db']
}
2. Generate:
./ace api
./ace migrate
./ace serve3. You now have:
- User registration & login
- Product CRUD API
- Category management
- Review system (users can review products)
- All relationships working
- Token authentication on all endpoints
Example API calls:
# Register and login (get token)
TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"pass123"}' \
| jq -r '.access_token')
# Create a product
curl -X POST http://localhost:8080/api/products/store \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"iPhone 15","price":999.99,"stock":100,"category_id":1}'
# Add a review
curl -X POST http://localhost:8080/api/reviews/store \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"product_id":1,"rating":5,"comment":"Great product!"}'
# Get product with reviews
curl http://localhost:8080/api/products/reviews/1 \
-H "Authorization: Bearer $TOKEN"Done. Full e-commerce API in minutes.
Perfect for:
- REST APIs with authentication needs
- Admin panels & dashboards
- Membership/subscription sites
- Multi-tenant applications
- Internal tools & utilities
- MVPs & prototypes
- Microservices
Not ideal for:
- Server-side rendered websites
- GraphQL APIs
- Real-time apps (WebSocket-heavy)
- Non-database-driven applications
- PHP 8.1+
- MySQL 5.7+ or SQLite 3
- Composer
- OpenSSL (for key generation)
Optional:
- Google Authenticator or Authy (for 2FA)
"Invalid or expired token"
- Token expired (1 hour for access token) β Use refresh token
- Token revoked (logged out) β Login again
- Wrong APP_KEY in .env β Regenerate tokens
"APP_KEY not set"
- Generate key:
openssl rand -base64 32 - Add to .env:
APP_KEY=<your_key>
"2FA code invalid"
- Check time sync on your device (TOTP is time-based)
- Use backup code if phone is unavailable
- Disable and re-enable 2FA
"Database connection failed"
- Check DB credentials in .env
- Ensure database exists
- Check DB server is running
LGPL-3.0-or-later
ACE = Absolute Simplicity + Complete Authentication
Most APIs need the same things:
- User accounts
- Login/logout
- Protected endpoints
- Role-based access
- Audit logs
ACE gives you all of this by default. No configuration, no packages, no decisions.
One schema file. Three commands. Production-ready API.
Stop building auth systems. Start building features.