wheels generate api-resource
Generate a complete RESTful API controller with advanced features like pagination, filtering, sorting, and authentication.
Synopsis
wheels generate api-resource [name] [options]
wheels g api-resource [name] [options]Description
The wheels generate api-resource command creates a production-ready RESTful API controller optimized for JSON APIs. It generates API-specific controllers with no view rendering logic, including optional features like pagination, filtering, sorting, authentication, and API versioning.
The generated controllers use provides("json") and renderWith() to return JSON responses with proper HTTP status codes for REST operations.
Arguments
name
Resource name (singular or plural)
Required
Options
--version
API version (v1, v2, etc.)
v1
--format
Response format (json, xml)
json
--auth
Include authentication
false
--pagination
Include pagination
true
--filtering
Include filtering
true
--sorting
Include sorting
true
--skipModel
Skip model generation
false
--skipMigration
Skip migration generation
false
--skipTests
Skip test generation
false
--namespace
API namespace
api
--docs
Generate API documentation template
false
--force
Overwrite existing files
false
CommandBox Parameter Syntax
This command supports multiple parameter formats:
Named parameters:
name=value(e.g.,name=products,version="v2")Flag parameters:
--flagequalsflag=true(e.g.,--authequalsauth=true)Flag with value:
--flag=valueequalsflag=value(e.g.,--version=v2)
Parameter Mixing Rules:
✅ ALLOWED:
All positional:
wheels generate api-resource productsAll named:
name=products version="v2" auth=truePositional + flags:
wheels generate api-resource products --auth --skipModel
❌ NOT ALLOWED:
Positional + named:
wheels generate api-resource products version="v2"(causes error)
Recommendation: Use positional for name + flags for options: wheels generate api-resource products --auth --version=v2
Examples
Basic API Controller
Generate a simple API controller:
# Positional name only
wheels generate api-resource productsCreates:
/models/Product.cfc- Model file/controllers/api/v1/Products.cfc- Versioned API controller with pagination, filtering, sorting
Without Advanced Features
Generate a minimal API controller without optional features:
# Using named parameters (all named)
wheels g api-resource name=products pagination=false filtering=false sorting=false
# OR using flags (positional + flags) - RECOMMENDED
wheels g api-resource products --pagination=false --filtering=false --sorting=falseCreates a simple controller with only basic CRUD operations.
With Authentication
Generate API controller with authentication:
# Using flag
wheels generate api-resource products --authIncludes Bearer token authentication that requires Authorization header for create, update, delete actions.
Custom Version and Namespace
Generate API controller with custom versioning:
# Using flags with values
wheels generate api-resource products --version=v2 --namespace=publicCreates /controllers/public/v2/Products.cfc
Skip Model Generation
Generate only the controller (model already exists):
# Using flag
wheels generate api-resource products --skipModelComplete Setup with Documentation
Generate everything with API documentation:
# Multiple flags
wheels generate api-resource products --auth --docsCreates:
/models/Product.cfc/controllers/api/v1/Products.cfcwith authentication/app/docs/api/products.md- API documentation
Generated Controller Features
With All Features Enabled
wheels generate api-resource products --auth --pagination --filtering --sortingGenerates:
component extends="wheels.Controller" {
function config() {
provides("json");
filters(through="setJsonResponse");
filters(through="authenticate", except="index,show");
}
/**
* GET /products
* Supports: ?page=1&perPage=25&sort=name,-price&filter[name]=widget
*/
function index() {
local.page = params.page ?: 1;
local.perPage = params.perPage ?: 25;
local.options = {};
local.options.page = local.page;
local.options.perPage = local.perPage;
if (structKeyExists(params, "sort")) {
local.options.order = parseSort(params.sort);
}
if (structKeyExists(params, "filter")) {
local.options.where = parseFilter(params.filter);
}
local.products = model("Product").findAll(argumentCollection=local.options);
local.response = {
data = local.products,
meta = {
pagination = {
page = local.products.currentPage ?: local.page,
perPage = local.perPage,
total = local.products.totalRecords ?: 0,
pages = local.products.totalPages ?: 1
}
}
};
renderWith(data=local.response);
}
function show() { /* ... */ }
function create() { /* ... */ }
function update() { /* ... */ }
function delete() { /* ... */ }
// Helper methods for pagination, filtering, sorting, auth
private function authenticate() { /* ... */ }
private function parseSort(required string sort) { /* ... */ }
private function parseFilter(required struct filter) { /* ... */ }
}Adding Routes
After generating your API resource, add routes to /config/routes.cfm:
Default Namespaced Routes
// Add inside mapper() block
namespace(name="api", function() {
namespace(name="v1", function() {
resources(name="products", except="new,edit");
});
});Creates routes:
GET /api/v1/productsGET /api/v1/products/:keyPOST /api/v1/productsPUT /api/v1/products/:keyDELETE /api/v1/products/:key
Custom Version
namespace(name="api", function() {
namespace(name="v2", function() {
resources(name="products", except="new,edit");
});
});No Namespace
If you used --namespace="":
resources(name="products", except="new,edit");Feature Details
Pagination
When --pagination is enabled (default):
Request:
curl "http://localhost:8080/api/v1/products?page=2&perPage=10"Response:
{
"data": [ /* products */ ],
"meta": {
"pagination": {
"page": 2,
"perPage": 10,
"total": 100,
"pages": 10
}
}
}Filtering
When --filtering is enabled (default):
Request:
curl "http://localhost:8080/api/v1/products?filter[name]=widget&filter[minPrice]=10"The generated controller includes a parseFilter() method with TODO comments for you to implement your filtering logic.
Sorting
When --sorting is enabled (default):
Request:
# Sort by name ascending, then price descending
curl "http://localhost:8080/api/v1/products?sort=name,-price"The - prefix indicates descending order.
Authentication
When --auth is enabled:
Request:
curl -X POST http://localhost:8080/api/v1/products \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{"product":{"name":"Widget"}}'The generated controller includes authentication methods that you need to implement with your actual token validation logic.
HTTP Status Codes
The generated controller uses proper REST HTTP status codes:
index
200 OK
-
show
200 OK
404 Not Found
create
201 Created
422 Unprocessable Entity
update
200 OK
404 Not Found, 422 Unprocessable Entity
delete
204 No Content
404 Not Found
auth failure
-
401 Unauthorized
Testing Your API
Basic Requests
# List products with pagination
curl "http://localhost:8080/api/v1/products?page=1&perPage=25"
# Get specific product
curl http://localhost:8080/api/v1/products/1
# Create product
curl -X POST http://localhost:8080/api/v1/products \
-H "Content-Type: application/json" \
-d '{"product":{"name":"Widget","price":29.99}}'
# Update product
curl -X PUT http://localhost:8080/api/v1/products/1 \
-H "Content-Type: application/json" \
-d '{"product":{"price":39.99}}'
# Delete product
curl -X DELETE http://localhost:8080/api/v1/products/1With Filtering and Sorting
# Filter and sort
curl "http://localhost:8080/api/v1/products?filter[name]=widget&sort=-createdAt"
# Pagination with filters
curl "http://localhost:8080/api/v1/products?page=1&perPage=10&filter[minPrice]=20&sort=name"With Authentication
# With Bearer token
curl -X POST http://localhost:8080/api/v1/products \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"product":{"name":"Widget"}}'Example Responses
Success with Pagination (200 OK):
{
"data": [
{
"id": 1,
"name": "Widget",
"price": 29.99,
"createdAt": "2023-01-01T12:00:00Z",
"updatedAt": "2023-01-01T12:00:00Z"
}
],
"meta": {
"pagination": {
"page": 1,
"perPage": 25,
"total": 1,
"pages": 1
}
}
}Validation Error (422):
{
"error": "Validation failed",
"errors": [
{
"property": "name",
"message": "This field is required"
}
]
}Not Found (404):
{
"error": "Record not found"
}Unauthorized (401):
{
"error": "Unauthorized"
}Customization
Implementing Filter Logic
Edit the generated parseFilter() method:
private function parseFilter(required struct filter) {
local.where = [];
local.params = {};
if (structKeyExists(arguments.filter, "name")) {
arrayAppend(local.where, "name LIKE :name");
local.params.name = "%#arguments.filter.name#%";
}
if (structKeyExists(arguments.filter, "minPrice")) {
arrayAppend(local.where, "price >= :minPrice");
local.params.minPrice = arguments.filter.minPrice;
}
if (structKeyExists(arguments.filter, "category")) {
arrayAppend(local.where, "category = :category");
local.params.category = arguments.filter.category;
}
return arrayLen(local.where) ? arrayToList(local.where, " AND ") : "";
}Implementing Authentication
Edit the generated isValidToken() method:
private function isValidToken(required string token) {
// Example: Check against database
local.apiKey = model("ApiKey").findOne(where="token = :token", token=arguments.token);
if (isObject(local.apiKey) && local.apiKey.active) {
// Store user context in session/request
request.user = local.apiKey.user();
return true;
}
return false;
}Adding More Sortable Fields
Edit the parseSort() method:
private function parseSort(required string sort) {
local.allowedFields = ["id", "name", "price", "category", "createdAt", "updatedAt"];
// ... rest of method
}Best Practices
Use Versioning: Always version your APIs (
--version=v1)Enable Pagination: Prevent performance issues with large datasets
Add Authentication: Secure your API endpoints with
--authDocument Your API: Use
--docsflag and keep documentation updatedImplement Filtering: Customize
parseFilter()for your model fieldsWhitelist Sort Fields: Only allow sorting on indexed fields
Use Proper Status Codes: 201 for creation, 204 for deletion
Return Error Details: Always include error messages for 4xx/5xx
Rate Limiting: Consider adding rate limiting for public APIs
CORS Headers: Add CORS support for browser-based clients
Comparison with Other Commands
Feature
api-resource
controller --api
scaffold
Generates model
Optional
No
Yes
Generates views
No
No
Yes
Actions
REST only
REST only
Full CRUD
Format
Configurable
JSON only
HTML + JSON
Versioning
Yes
No
No
Pagination
Optional
No
No
Filtering
Optional
No
No
Sorting
Optional
No
No
Authentication
Optional
No
No
Best for
Production APIs
Simple APIs
Full-stack apps
See Also
wheels generate controller - Generate standard controllers
wheels generate model - Generate models
wheels generate scaffold - Generate full CRUD resources
Wheels REST Documentation - REST API best practices
Last updated
Was this helpful?

