Skip to content

Command Line Tools

wheels generate api-resource

Generate a complete RESTful API controller with advanced features like pagination, filtering, sorting, and authentication.

Terminal window
wheels generate api-resource [name] [options]
wheels g api-resource [name] [options]

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.

ArgumentDescriptionDefault
nameResource name (singular or plural)Required
OptionDescriptionDefault
--versionAPI version (v1, v2, etc.)v1
--formatResponse format (json, xml)json
--authInclude authenticationfalse
--paginationInclude paginationtrue
--filteringInclude filteringtrue
--sortingInclude sortingtrue
--skipModelSkip model generationfalse
--skipMigrationSkip migration generationfalse
--skipTestsSkip test generationfalse
--namespaceAPI namespaceapi
--docsGenerate API documentation templatefalse
--forceOverwrite existing filesfalse

This command supports multiple parameter formats:

  • Named parameters: name=value (e.g., name=products, version="v2")
  • Flag parameters: --flag equals flag=true (e.g., --auth equals auth=true)
  • Flag with value: --flag=value equals flag=value (e.g., --version=v2)

Parameter Mixing Rules:

ALLOWED:

  • All positional: wheels generate api-resource products
  • All named: name=products version="v2" auth=true
  • Positional + 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


Generate a simple API controller:

Terminal window
# Positional name only
wheels generate api-resource products

Creates:

  • /models/Product.cfc - Model file
  • /controllers/api/v1/Products.cfc - Versioned API controller with pagination, filtering, sorting

Generate a minimal API controller without optional features:

Terminal window
# 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=false

Creates a simple controller with only basic CRUD operations.

Generate API controller with authentication:

Terminal window
# Using flag
wheels generate api-resource products --auth

Includes Bearer token authentication that requires Authorization header for create, update, delete actions.

Generate API controller with custom versioning:

Terminal window
# Using flags with values
wheels generate api-resource products --version=v2 --namespace=public

Creates /controllers/public/v2/Products.cfc

Generate only the controller (model already exists):

Terminal window
# Using flag
wheels generate api-resource products --skipModel

Generate everything with API documentation:

Terminal window
# Multiple flags
wheels generate api-resource products --auth --docs

Creates:

  • /models/Product.cfc
  • /controllers/api/v1/Products.cfc with authentication
  • /app/docs/api/products.md - API documentation
Terminal window
wheels generate api-resource products --auth --pagination --filtering --sorting

Generates:

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) { /* ... */ }
}

After generating your API resource, add routes to /config/routes.cfm:

// Add inside mapper() block
namespace(name="api", function() {
namespace(name="v1", function() {
resources(name="products", except="new,edit");
});
});

Creates routes:

  • GET /api/v1/products
  • GET /api/v1/products/:key
  • POST /api/v1/products
  • PUT /api/v1/products/:key
  • DELETE /api/v1/products/:key
namespace(name="api", function() {
namespace(name="v2", function() {
resources(name="products", except="new,edit");
});
});

If you used --namespace="":

resources(name="products", except="new,edit");

When --pagination is enabled (default):

Request:

Terminal window
curl "http://localhost:8080/api/v1/products?page=2&perPage=10"

Response:

{
"data": [ /* products */ ],
"meta": {
"pagination": {
"page": 2,
"perPage": 10,
"total": 100,
"pages": 10
}
}
}

When --filtering is enabled (default):

Request:

Terminal window
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.

When --sorting is enabled (default):

Request:

Terminal window
# Sort by name ascending, then price descending
curl "http://localhost:8080/api/v1/products?sort=name,-price"

The - prefix indicates descending order.

When --auth is enabled:

Request:

Terminal window
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.

The generated controller uses proper REST HTTP status codes:

ActionSuccess StatusError Status
index200 OK-
show200 OK404 Not Found
create201 Created422 Unprocessable Entity
update200 OK404 Not Found, 422 Unprocessable Entity
delete204 No Content404 Not Found
auth failure-401 Unauthorized
Terminal window
# 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/1
Terminal window
# 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"
Terminal window
# 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"}}'

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"
}

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 ") : "";
}

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;
}

Edit the parseSort() method:

private function parseSort(required string sort) {
local.allowedFields = ["id", "name", "price", "category", "createdAt", "updatedAt"];
// ... rest of method
}
  1. Use Versioning: Always version your APIs (--version=v1)
  2. Enable Pagination: Prevent performance issues with large datasets
  3. Add Authentication: Secure your API endpoints with --auth
  4. Document Your API: Use --docs flag and keep documentation updated
  5. Implement Filtering: Customize parseFilter() for your model fields
  6. Whitelist Sort Fields: Only allow sorting on indexed fields
  7. Use Proper Status Codes: 201 for creation, 204 for deletion
  8. Return Error Details: Always include error messages for 4xx/5xx
  9. Rate Limiting: Consider adding rate limiting for public APIs
  10. CORS Headers: Add CORS support for browser-based clients
Featureapi-resourcecontroller --apiscaffold
Generates modelOptionalNoYes
Generates viewsNoNoYes
ActionsREST onlyREST onlyFull CRUD
FormatConfigurableJSON onlyHTML + JSON
VersioningYesNoNo
PaginationOptionalNoNo
FilteringOptionalNoNo
SortingOptionalNoNo
AuthenticationOptionalNoNo
Best forProduction APIsSimple APIsFull-stack apps