Data & Services - REST Services

Register and manage RESTful web services in ColdFusion

Overview

The REST Services section in ColdFusion Administrator allows you to register, configure, and manage RESTful web services created with ColdFusion components (CFCs). REST services provide a lightweight, HTTP-based API for client applications to interact with your ColdFusion backend.

REST Service Registration

Configure REST services by mapping virtual paths to ColdFusion component directories.

Service Name

Purpose
Unique identifier for the REST service
Format
Alphanumeric string, no spaces

Used in API URLs and administrator interface to identify the service.

Service Mapping

Purpose
Virtual directory path for REST endpoints
Example
/rest/api

URL path prefix for accessing REST endpoints (e.g., https://example.com/rest/api/users)

Physical Path

Purpose
File system location of REST CFCs
Example
/var/www/myapp/api

Directory containing REST-enabled CFC files with remote access="remote" methods.

Active Status

Purpose
Enable or disable the REST service
Default
Enabled when registered

Deactivate services without removing registration - useful for maintenance windows.

Creating REST CFCs

Build RESTful web services using ColdFusion components with REST annotations.

Basic REST CFC Example

Simple REST service demonstrating GET, POST, PUT, and DELETE operations:

User REST API
component restpath="/users" rest="true" {

  // GET /rest/api/users
  remote array function getUsers()
    httpmethod="GET"
    restpath=""
    produces="application/json" {

    var users = queryExecute("SELECT * FROM users");
    return queryToArray(users);
  }

  // GET /rest/api/users/{id}
  remote struct function getUser(required numeric id)
    httpmethod="GET"
    restpath="/{id}"
    produces="application/json" {

    var user = queryExecute(
      "SELECT * FROM users WHERE id = :id",
      {id: arguments.id}
    );

    if (user.recordCount == 0) {
      restSetResponse({
        status: 404,
        content: {error: "User not found"}
      });
      return {};
    }

    return queryRowToStruct(user, 1);
  }

  // POST /rest/api/users
  remote struct function createUser(
    required string name,
    required string email
  )
    httpmethod="POST"
    restpath=""
    produces="application/json" {

    // Validate input
    if (!isValid("email", arguments.email)) {
      restSetResponse({
        status: 400,
        content: {error: "Invalid email address"}
      });
      return {};
    }

    var result = queryExecute(
      "INSERT INTO users (name, email) VALUES (:name, :email)",
      {name: arguments.name, email: arguments.email}
    );

    restSetResponse({
      status: 201,
      content: {message: "User created successfully"}
    });

    return {id: result.generatedKey, name: arguments.name, email: arguments.email};
  }

  // PUT /rest/api/users/{id}
  remote struct function updateUser(
    required numeric id,
    required string name,
    required string email
  )
    httpmethod="PUT"
    restpath="/{id}"
    produces="application/json" {

    queryExecute(
      "UPDATE users SET name = :name, email = :email WHERE id = :id",
      {id: arguments.id, name: arguments.name, email: arguments.email}
    );

    return {id: arguments.id, name: arguments.name, email: arguments.email};
  }

  // DELETE /rest/api/users/{id}
  remote struct function deleteUser(required numeric id)
    httpmethod="DELETE"
    restpath="/{id}"
    produces="application/json" {

    queryExecute("DELETE FROM users WHERE id = :id", {id: arguments.id});

    restSetResponse({
      status: 200,
      content: {message: "User deleted successfully"}
    });

    return {};
  }

  // Helper function to convert query row to struct
  private struct function queryRowToStruct(required query qry, required numeric row) {
    var result = {};
    for (var col in listToArray(arguments.qry.columnList)) {
      result[col] = arguments.qry[col][arguments.row];
    }
    return result;
  }

  // Helper function to convert query to array of structs
  private array function queryToArray(required query qry) {
    var result = [];
    for (var i = 1; i <= arguments.qry.recordCount; i++) {
      arrayAppend(result, queryRowToStruct(arguments.qry, i));
    }
    return result;
  }
}
<cfcomponent restpath="/users" rest="true">

  <!--- GET /rest/api/users --->
  <cffunction name="getUsers" access="remote" returntype="array"
              httpmethod="GET" restpath="" produces="application/json">

    <cfquery name="users">
      SELECT * FROM users
    </cfquery>

    <cfreturn queryToArray(users)>
  </cffunction>

  <!--- GET /rest/api/users/{id} --->
  <cffunction name="getUser" access="remote" returntype="struct"
              httpmethod="GET" restpath="/{id}" produces="application/json">
    <cfargument name="id" type="numeric" required="true">

    <cfquery name="user">
      SELECT * FROM users
      WHERE id = <cfqueryparam value="#arguments.id#" cfsqltype="cf_sql_integer">
    </cfquery>

    <cfif user.recordCount eq 0>
      <cfset restSetResponse({
        status: 404,
        content: {error: "User not found"}
      })>
      <cfreturn {}>
    </cfif>

    <cfreturn queryRowToStruct(user, 1)>
  </cffunction>

  <!--- POST /rest/api/users --->
  <cffunction name="createUser" access="remote" returntype="struct"
              httpmethod="POST" restpath="" produces="application/json">
    <cfargument name="name" type="string" required="true">
    <cfargument name="email" type="string" required="true">

    <!--- Validate input --->
    <cfif NOT isValid("email", arguments.email)>
      <cfset restSetResponse({
        status: 400,
        content: {error: "Invalid email address"}
      })>
      <cfreturn {}>
    </cfif>

    <cfquery name="result" result="insertResult">
      INSERT INTO users (name, email)
      VALUES (
        <cfqueryparam value="#arguments.name#" cfsqltype="cf_sql_varchar">,
        <cfqueryparam value="#arguments.email#" cfsqltype="cf_sql_varchar">
      )
    </cfquery>

    <cfset restSetResponse({
      status: 201,
      content: {message: "User created successfully"}
    })>

    <cfreturn {
      id: insertResult.generatedKey,
      name: arguments.name,
      email: arguments.email
    }>
  </cffunction>

  <!--- PUT /rest/api/users/{id} --->
  <cffunction name="updateUser" access="remote" returntype="struct"
              httpmethod="PUT" restpath="/{id}" produces="application/json">
    <cfargument name="id" type="numeric" required="true">
    <cfargument name="name" type="string" required="true">
    <cfargument name="email" type="string" required="true">

    <cfquery>
      UPDATE users
      SET name = <cfqueryparam value="#arguments.name#" cfsqltype="cf_sql_varchar">,
          email = <cfqueryparam value="#arguments.email#" cfsqltype="cf_sql_varchar">
      WHERE id = <cfqueryparam value="#arguments.id#" cfsqltype="cf_sql_integer">
    </cfquery>

    <cfreturn {
      id: arguments.id,
      name: arguments.name,
      email: arguments.email
    }>
  </cffunction>

  <!--- DELETE /rest/api/users/{id} --->
  <cffunction name="deleteUser" access="remote" returntype="struct"
              httpmethod="DELETE" restpath="/{id}" produces="application/json">
    <cfargument name="id" type="numeric" required="true">

    <cfquery>
      DELETE FROM users
      WHERE id = <cfqueryparam value="#arguments.id#" cfsqltype="cf_sql_integer">
    </cfquery>

    <cfset restSetResponse({
      status: 200,
      content: {message: "User deleted successfully"}
    })>

    <cfreturn {}>
  </cffunction>

  <!--- Helper function to convert query row to struct --->
  <cffunction name="queryRowToStruct" access="private" returntype="struct">
    <cfargument name="qry" type="query" required="true">
    <cfargument name="row" type="numeric" required="true">

    <cfset var result = {}>
    <cfloop list="#arguments.qry.columnList#" index="col">
      <cfset result[col] = arguments.qry[col][arguments.row]>
    </cfloop>

    <cfreturn result>
  </cffunction>

  <!--- Helper function to convert query to array of structs --->
  <cffunction name="queryToArray" access="private" returntype="array">
    <cfargument name="qry" type="query" required="true">

    <cfset var result = []>
    <cfloop from="1" to="#arguments.qry.recordCount#" index="i">
      <cfset arrayAppend(result, queryRowToStruct(arguments.qry, i))>
    </cfloop>

    <cfreturn result>
  </cffunction>

</cfcomponent>
REST Annotations:
  • rest="true" - Enable REST functionality on the component
  • restpath="/users" - Base URL path for the resource
  • httpmethod="GET|POST|PUT|DELETE" - HTTP method for the function
  • restpath="/{id}" - URL parameters in curly braces
  • produces="application/json" - Response content type
  • consumes="application/json" - Request content type (optional)

Security Best Practices

Essential security measures for production REST APIs.

Always Use HTTPS

PurposeEncrypt API traffic to prevent interception and tampering
RecommendationRequired for production - Never use HTTP for APIs handling sensitive data
Security Critical: API credentials and data transmitted over HTTP can be intercepted by attackers. Always enforce HTTPS in production.

Implement Authentication

PurposeVerify identity of API consumers before granting access
OptionsJWT Tokens: Stateless authentication with signed tokens
API Keys: Simple key-based authentication
OAuth 2.0: Industry-standard for third-party access
Best Practices:
  • Use JWT tokens with short expiration times (15-60 minutes)
  • Implement refresh token rotation for long-lived sessions
  • Store API keys securely - never commit to version control
  • Rate limit failed authentication attempts

Input Validation & Sanitization

PurposePrevent SQL injection, XSS, and other injection attacks
TechniquesAlways use cfqueryparam for database queries
Validate data types with isValid()
Sanitize HTML output with encodeForHTML()
Whitelist allowed values instead of blacklisting
Critical: Never trust client input. Always validate and sanitize all parameters before processing.

Rate Limiting

PurposePrevent API abuse and denial-of-service attacks
RecommendationAuthenticated users: 1000 requests/hour
Unauthenticated: 100 requests/hour
Failed logins: 5 attempts per 15 minutes
Implementation: Use in-memory cache or Redis to track request counts per API key/IP address.

CORS Configuration

PurposeControl which domains can access your API from browsers
RecommendationWhitelist specific origins - Never use Access-Control-Allow-Origin: * for APIs with authentication
Security Risk: Overly permissive CORS allows malicious websites to make requests to your API using users' credentials.

HTTP Status Codes

PurposeCommunicate API response status clearly to clients
Common Codes200 OK: Successful GET, PUT, DELETE
201 Created: Successful POST creating new resource
400 Bad Request: Invalid input parameters
401 Unauthorized: Missing or invalid authentication
403 Forbidden: Valid auth but insufficient permissions
404 Not Found: Resource does not exist
500 Internal Server Error: Server-side error
Best Practice: Always set appropriate status codes using restSetResponse() - don't rely on defaults.

Related Resources