LogoLogo
HomeAPIBlog
3.0.0-SNAPSHOT
3.0.0-SNAPSHOT
  • INTRODUCTION
    • Getting Started
      • Running Local Development Servers
      • Beginner Tutorial: Hello World
      • Beginner Tutorial: Hello Database
      • Tutorial: Wheels, AJAX, and You
    • Frameworks and Wheels
    • Requirements
    • Manual Installation
    • Upgrading
    • Screencasts
  • Command Line Tools
    • CLI Overview
    • Quick Start Guide
    • Command Reference
      • Core Commands
        • wheels init
        • wheels info
        • wheels reload
        • wheels deps
        • wheels destroy
        • wheels watch
      • Code Generation
        • wheels generate app
        • wheels generate app-wizard
        • wheels generate controller
        • wheels generate model
        • wheels generate view
        • wheels generate property
        • wheels generate route
        • wheels generate resource
        • wheels generate api-resource
        • wheels generate frontend
        • wheels generate test
        • wheels generate snippets
        • wheels scaffold
      • Database Commands
        • wheels dbmigrate info
        • wheels dbmigrate latest
        • wheels dbmigrate up
        • wheels dbmigrate down
        • wheels dbmigrate reset
        • wheels dbmigrate exec
        • wheels dbmigrate create blank
        • wheels dbmigrate create table
        • wheels dbmigrate create column
        • wheels dbmigrate remove table
        • wheels db schema
        • wheels db seed
      • Testing Commands
        • wheels test
        • wheels test run
        • wheels test coverage
        • wheels test debug
      • Configuration Commands
        • wheels config list
        • wheels config set
        • wheels config env
      • Environment Management
        • wheels env
        • wheels env setup
        • wheels env list
        • wheels env switch
      • Plugin Management
        • wheels plugins
        • wheels plugins list
        • wheels plugins install
        • wheels plugins remove
      • Code Analysis
        • wheels analyze
        • wheels analyze code
        • wheels analyze performance
        • wheels analyze security
      • Security Commands
        • wheels security
        • wheels security scan
      • Performance Commands
        • wheels optimize
        • wheels optimize performance
      • Documentation Commands
        • wheels docs
        • wheels docs generate
        • wheels docs serve
      • CI/CD Commands
        • wheels ci init
      • Docker Commands
        • wheels docker init
        • wheels docker deploy
      • Deployment Commands
        • wheels deploy
        • wheels deploy audit
        • wheels deploy exec
        • wheels deploy hooks
        • wheels deploy init
        • wheels deploy lock
        • wheels deploy logs
        • wheels deploy proxy
        • wheels deploy push
        • wheels deploy rollback
        • wheels deploy secrets
        • wheels deploy setup
        • wheels deploy status
        • wheels deploy stop
    • CLI Development Guides
      • Creating Commands
      • Service Architecture
      • Migrations Guide
      • Testing Guide
  • Working with Wheels
    • Conventions
    • Configuration and Defaults
    • Directory Structure
    • Switching Environments
    • Testing Your Application
    • Using the Test Environment
    • Contributing to Wheels
    • Submitting Pull Requests
    • Documenting your Code
  • Handling Requests with Controllers
    • Request Handling
    • Rendering Content
    • Redirecting Users
    • Sending Files
    • Sending Email
    • Responding with Multiple Formats
    • Using the Flash
    • Using Filters
    • Verification
    • Event Handlers
    • Routing
    • URL Rewriting
      • Apache
      • IIS
      • Tomcat
      • Nginx
    • Obfuscating URLs
    • Caching
    • Nesting Controllers
    • CORS Requests
  • Displaying Views to Users
    • Pages
    • Partials
    • Linking Pages
    • Layouts
    • Form Helpers and Showing Errors
    • Displaying Links for Pagination
    • Date, Media, and Text Helpers
    • Creating Custom View Helpers
    • Localization
  • Database Interaction Through Models
    • Object Relational Mapping
    • Creating Records
    • Reading Records
    • Updating Records
    • Deleting Records
    • Column Statistics
    • Dynamic Finders
    • Getting Paginated Data
    • Associations
    • Nested Properties
    • Object Validation
    • Object Callbacks
    • Calculated Properties
    • Transactions
    • Dirty Records
    • Soft Delete
    • Automatic Time Stamps
    • Database Migrations
      • Migrations in Production
    • Using Multiple Data Sources
  • Plugins
    • Installing and Using Plugins
    • Developing Plugins
    • Publishing Plugins
  • Project Documentation
    • Overview
  • External Links
    • Source Code
    • Issue Tracker
    • Sponsor Us
    • Community
Powered by GitBook
LogoLogo
On this page
  • Synopsis
  • Description
  • Arguments
  • Options
  • Examples
  • Basic Resource
  • API Resource
  • Nested Resource
  • Generated Files
  • Model
  • Controller
  • Views
  • Migration
  • Routes
  • API Resource Generation
  • API Controller
  • Nested Resources
  • Generate Nested Resource
  • Nested Model
  • Nested Routes
  • Property Types
  • Supported Types
  • Property Options
  • Advanced Options
  • Skip Components
  • Namespace Resources
  • Custom Templates
  • Testing
  • Generated Tests
  • Best Practices
  • Common Patterns
  • Soft Delete Resource
  • Publishable Resource
  • User-Owned Resource
  • Hierarchical Resource
  • Customization
  • Custom Resource Templates
  • Template Variables
  • See Also

Was this helpful?

Edit on GitHub
Export as PDF
  1. Command Line Tools
  2. Command Reference
  3. Code Generation

wheels generate resource

Generate a complete RESTful resource with model, controller, views, and routes.

Synopsis

wheels generate resource [name] [properties] [options]
wheels g resource [name] [properties] [options]

Description

The wheels generate resource command creates a complete RESTful resource including model, controller with all CRUD actions, views, routes, and optionally database migrations and tests. It's a comprehensive generator that sets up everything needed for a functioning resource.

Arguments

Argument
Description
Default

name

Resource name (typically singular)

Required

properties

Property definitions (name:type)

Options

Option
Description
Default

--skip-model

Skip model generation

false

--skip-controller

Skip controller generation

false

--skip-views

Skip view generation

false

--skip-route

Skip route generation

false

--skip-migration

Skip migration generation

false

--skip-tests

Skip test generation

false

--api

Generate API-only resource

false

--namespace

Namespace for the resource

--parent

Parent resource for nesting

--force

Overwrite existing files

false

--help

Show help information

Examples

Basic Resource

wheels generate resource product name:string price:float description:text

Generates:

  • Model: /models/Product.cfc

  • Controller: /controllers/Products.cfc

  • Views: /views/products/ (index, show, new, edit, _form)

  • Route: resources("products") in /config/routes.cfm

  • Migration: /db/migrate/[timestamp]_create_products.cfc

  • Tests: /tests/models/ProductTest.cfc, /tests/controllers/ProductsTest.cfc

API Resource

wheels generate resource api/product name:string price:float --api

Generates:

  • Model: /models/Product.cfc

  • Controller: /controllers/api/Products.cfc (JSON responses only)

  • Route: resources(name="products", except="new,edit") in API namespace

  • Migration: /db/migrate/[timestamp]_create_products.cfc

  • Tests: API-focused test files

Nested Resource

wheels generate resource comment content:text approved:boolean --parent=post

Generates nested structure with proper associations and routing.

Generated Files

Model

/models/Product.cfc:

component extends="Model" {
    
    function init() {
        // Properties
        property(name="name", sql="name");
        property(name="price", sql="price");
        property(name="description", sql="description");
        
        // Validations
        validatesPresenceOf(properties="name,price");
        validatesNumericalityOf(property="price", greaterThan=0);
        validatesLengthOf(property="name", maximum=255);
        
        // Callbacks
        beforeSave("sanitizeInput");
    }
    
    private function sanitizeInput() {
        this.name = Trim(this.name);
        if (StructKeyExists(this, "description")) {
            this.description = Trim(this.description);
        }
    }
    
}

Controller

/controllers/Products.cfc:

component extends="Controller" {
    
    function init() {
        // Filters
        filters(through="findProduct", only="show,edit,update,delete");
    }
    
    function index() {
        products = model("Product").findAll(order="createdAt DESC");
    }
    
    function show() {
        // Product loaded by filter
    }
    
    function new() {
        product = model("Product").new();
    }
    
    function create() {
        product = model("Product").new(params.product);
        
        if (product.save()) {
            flashInsert(success="Product was created successfully.");
            redirectTo(route="product", key=product.id);
        } else {
            renderView(action="new");
        }
    }
    
    function edit() {
        // Product loaded by filter
    }
    
    function update() {
        if (product.update(params.product)) {
            flashInsert(success="Product was updated successfully.");
            redirectTo(route="product", key=product.id);
        } else {
            renderView(action="edit");
        }
    }
    
    function delete() {
        if (product.delete()) {
            flashInsert(success="Product was deleted successfully.");
        } else {
            flashInsert(error="Product could not be deleted.");
        }
        redirectTo(route="products");
    }
    
    // Filters
    private function findProduct() {
        product = model("Product").findByKey(params.key);
        if (!IsObject(product)) {
            flashInsert(error="Product not found.");
            redirectTo(route="products");
        }
    }
    
}

Views

/views/products/index.cfm:

<h1>Products</h1>

<p>
    #linkTo(route="newProduct", text="New Product", class="btn btn-primary")#
</p>

<cfif products.recordCount>
    <table class="table table-striped">
        <thead>
            <tr>
                <th>Name</th>
                <th>Price</th>
                <th>Created</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            <cfoutput query="products">
                <tr>
                    <td>#linkTo(route="product", key=products.id, text=products.name)#</td>
                    <td>#dollarFormat(products.price)#</td>
                    <td>#dateFormat(products.createdAt, "mmm dd, yyyy")#</td>
                    <td>
                        #linkTo(route="editProduct", key=products.id, text="Edit", class="btn btn-sm btn-secondary")#
                        #linkTo(route="product", key=products.id, text="Delete", method="delete", confirm="Are you sure?", class="btn btn-sm btn-danger")#
                    </td>
                </tr>
            </cfoutput>
        </tbody>
    </table>
<cfelse>
    <p class="alert alert-info">No products found. #linkTo(route="newProduct", text="Create one now")#!</p>
</cfif>

/views/products/_form.cfm:

#errorMessagesFor("product")#

#startFormTag(route=formRoute, key=formKey, class="needs-validation")#
    
    <div class="mb-3">
        #textField(objectName="product", property="name", label="Product Name", class="form-control", required=true)#
    </div>
    
    <div class="mb-3">
        #numberField(objectName="product", property="price", label="Price", class="form-control", step="0.01", min="0", required=true)#
    </div>
    
    <div class="mb-3">
        #textArea(objectName="product", property="description", label="Description", class="form-control", rows=5)#
    </div>
    
    <div class="mb-3">
        #submitTag(value=submitValue, class="btn btn-primary")#
        #linkTo(route="products", text="Cancel", class="btn btn-secondary")#
    </div>
    
#endFormTag()#

Migration

/db/migrate/[timestamp]_create_products.cfc:

component extends="wheels.migrator.Migration" hint="Create products table" {
    
    function up() {
        transaction {
            createTable(name="products", force=true) {
                t.increments("id");
                t.string("name", limit=255, null=false);
                t.decimal("price", precision=10, scale=2, null=false);
                t.text("description");
                t.timestamps();
                t.index("name");
            };
        }
    }
    
    function down() {
        transaction {
            dropTable("products");
        }
    }
    
}

Routes

Added to /config/routes.cfm:

<cfset resources("products")>

API Resource Generation

API Controller

/controllers/api/Products.cfc:

component extends="Controller" {
    
    function init() {
        provides("json");
        filters(through="findProduct", only="show,update,delete");
    }
    
    function index() {
        products = model("Product").findAll(
            order="createdAt DESC",
            page=params.page ?: 1,
            perPage=params.perPage ?: 25
        );
        
        renderWith({
            data: products,
            meta: {
                page: products.currentPage,
                totalPages: products.totalPages,
                totalRecords: products.totalRecords
            }
        });
    }
    
    function show() {
        renderWith(product);
    }
    
    function create() {
        product = model("Product").new(params.product);
        
        if (product.save()) {
            renderWith(data=product, status=201);
        } else {
            renderWith(
                data={errors: product.allErrors()},
                status=422
            );
        }
    }
    
    function update() {
        if (product.update(params.product)) {
            renderWith(product);
        } else {
            renderWith(
                data={errors: product.allErrors()},
                status=422
            );
        }
    }
    
    function delete() {
        if (product.delete()) {
            renderWith(data={message: "Product deleted successfully"});
        } else {
            renderWith(
                data={error: "Could not delete product"},
                status=400
            );
        }
    }
    
    private function findProduct() {
        product = model("Product").findByKey(params.key);
        if (!IsObject(product)) {
            renderWith(
                data={error: "Product not found"},
                status=404
            );
        }
    }
    
}

Nested Resources

Generate Nested Resource

wheels generate resource review rating:integer comment:text --parent=product

Nested Model

Includes association:

component extends="Model" {
    
    function init() {
        belongsTo("product");
        
        property(name="rating", sql="rating");
        property(name="comment", sql="comment");
        property(name="productId", sql="product_id");
        
        validatesPresenceOf(properties="rating,comment,productId");
        validatesNumericalityOf(property="rating", greaterThanOrEqualTo=1, lessThanOrEqualTo=5);
    }
    
}

Nested Routes

<cfset resources("products")>
    <cfset resources("reviews")>
</cfset>

Property Types

Supported Types

Type
Database Type
Validation

string

VARCHAR(255)

Length validation

text

TEXT

None by default

integer

INT

Numerical validation

float

DECIMAL

Numerical validation

decimal

DECIMAL

Numerical validation

boolean

BOOLEAN

Boolean validation

date

DATE

Date format validation

datetime

DATETIME

DateTime validation

time

TIME

Time validation

binary

BLOB

None

Property Options

wheels generate resource user \
  email:string:required:unique \
  age:integer:min=18:max=120 \
  bio:text:limit=1000 \
  isActive:boolean:default=true

Advanced Options

Skip Components

# Generate only model and migration
wheels generate resource product name:string --skip-controller --skip-views --skip-route

# Generate only controller and views
wheels generate resource product --skip-model --skip-migration

Namespace Resources

wheels generate resource admin/product name:string --namespace=admin

Creates:

  • /controllers/admin/Products.cfc

  • /views/admin/products/

  • Namespaced routes

Custom Templates

wheels generate resource product name:string --template=custom

Testing

Generated Tests

Model Test (/tests/models/ProductTest.cfc):

component extends="wheels.Test" {
    
    function setup() {
        super.setup();
        model("Product").deleteAll();
    }
    
    function test_valid_product_saves() {
        product = model("Product").new(
            name="Test Product",
            price=19.99,
            description="Test description"
        );
        
        assert(product.save());
        assert(product.id > 0);
    }
    
    function test_requires_name() {
        product = model("Product").new(price=19.99);
        
        assert(!product.save());
        assert(ArrayLen(product.errorsOn("name")) > 0);
    }
    
    function test_requires_positive_price() {
        product = model("Product").new(name="Test", price=-10);
        
        assert(!product.save());
        assert(ArrayLen(product.errorsOn("price")) > 0);
    }
    
}

Controller Test (/tests/controllers/ProductsTest.cfc):

component extends="wheels.Test" {
    
    function test_index_returns_products() {
        products = createProducts(3);
        
        result = processRequest(route="products", method="GET");
        
        assert(result.status == 200);
        assert(Find("Products", result.body));
        assert(FindNoCase(products[1].name, result.body));
    }
    
    function test_create_valid_product() {
        params = {
            product: {
                name: "New Product",
                price: 29.99,
                description: "New product description"
            }
        };
        
        result = processRequest(route="products", method="POST", params=params);
        
        assert(result.status == 302);
        assert(model("Product").count() == 1);
    }
    
}

Best Practices

  1. Use singular names: product not products

  2. Define all properties: Include types and validations

  3. Add indexes: For frequently queried fields

  4. Include tests: Don't skip test generation

  5. Use namespaces: For admin or API resources

  6. Follow conventions: Stick to RESTful patterns

Common Patterns

Soft Delete Resource

wheels generate resource product name:string deletedAt:datetime:nullable

Publishable Resource

wheels generate resource post title:string content:text publishedAt:datetime:nullable status:string:default=draft

User-Owned Resource

wheels generate resource task title:string userId:integer:belongsTo=user completed:boolean:default=false

Hierarchical Resource

wheels generate resource category name:string parentId:integer:nullable:belongsTo=category

Customization

Custom Resource Templates

Create in ~/.wheels/templates/resources/:

custom-resource/
├── model.cfc
├── controller.cfc
├── views/
│   ├── index.cfm
│   ├── show.cfm
│   ├── new.cfm
│   ├── edit.cfm
│   └── _form.cfm
└── migration.cfc

Template Variables

Available in templates:

  • ${resourceName} - Singular name

  • ${resourceNamePlural} - Plural name

  • ${modelName} - Model class name

  • ${controllerName} - Controller class name

  • ${tableName} - Database table name

  • ${properties} - Array of property definitions

See Also

Previouswheels generate routeNextwheels generate api-resource

Last updated 2 days ago

Was this helpful?

- Interactive CRUD generation

- Generate models only

- Generate controllers only

- Generate API resources

- Generate routes only

wheels scaffold
wheels generate model
wheels generate controller
wheels generate api-resource
wheels generate route