Dear Couchers,

Lately, I've been building projects using reactive JS frameworks (such as AlpineJS, Vue, React), which has gotten me into the habit of building bespoke API endpoints for my Couch projects that I can interact with asynchronously. That is, I'd manually build a bunch of custom routes and tailor them to their respective templates with tags such as <cms:content_type 'application/json' />, <cms:gpc />, and outputting my response variables as JSON with <cms:show response as_json='1' />. It was very time-consuming and would become increasingly complex as a project grew.

I started to notice the patterns across these bespoke endpoints and was fortunate enough to come across the <cms:db_fields> here on the forum (thank you @trendoman :) ) -- which has led to my effort at building a Universal API for any CouchCMS project!

That is, we can simply define our editables, and, by definition of those editables (and inclusion of the referenced api.php file below), we can perform CRUD operations on any record within the system! That includes creating, listing, editing, and deleting, and the API is aware of our available editable fields dynamically (so no more bespoke endpoint crafting :) ).

Please let me know what you think! I'd love to see suggestions and holes poked in it where applicable. I'll keep updating the GitHub repo, so feel free to keep tabs on it there too.

** Currently looking into how to include <cms:related_pages> and <cms:reverse_related_pages> functionality, as well as creating/editing mosaic regions (we can currently list mosaics -- which was a little tricky!) -- working on systematizing the listing of records publicly per template (maybe via <cms:globals> definitions, so it can remain dynamic and not require bespoke files or code, etc). More to come over time :)

- - -

GitHub Repository
https://github.com/GroveOS/couchcms-api

Requirements:
- Routes addon must be enabled (couch/addons/kfunctions.php)
- K_API_KEY in couch/config.php (you should make this a nice long random string)

- - -

Authentication
By default, the system only allows:

(1) logged-in admins via browser
(2) command line curl or other HTTP requests containing a POSTed api_key that matches K_API_KEY in your couch/config.php file

But you can, of course, edit the api.php file to your needs.

- - -

API endpoint examples below for clonable templates of people.php and blog-posts.php

Listing
Code: Select all
api.php?q=v0/people/
api.php?q=v0/people/&id=242
api.php?q=v0/blog-posts/&caption=My%20caption%20here
api.php?q=v0/blog-posts/&limit=60
api.php?q=v0/people/&where=first_name.eq.Jane|last_name.eq.Doe


- - -

* Below, I'm using curl for command line use, but you could also send these as fetch requests via JS in the browser if you're logged in as admin.

Creating:
Code: Select all
curl "https://your-site.com/api.php?q=v0/people/create/" -d k_page_title=John%20Doe -d first_name=John -d last_name=Doe -d api_key={your-K_API_KEY-here}
curl "https://your-site.com/api.php?q=v0/blog-posts/create/" -d k_page_title=About%20our%20compny -d caption=written%20by%20matthew -d author=255 -d api_key={your-K_API_KEY-here}


Editing
Code: Select all
curl "https://your-site.com/api.php?q=v0/people/edit/&id=255" -d k_page_title=Matthew%20Larkin -d first_name=Matthew -d last_name=Larkin -d api_key={your-K_API_KEY-here}
curl "https://your-site.com/api.php?q=v0/blog-posts/edit/&id=688" -d k_page_title=About%20our%20company -d api_key={your-K_API_KEY-here}


- - -

You may notice, I'm using a lightweight Couch function for performing curl requests, <cms:call 'curl' />, and if you use that function elsewhere in your project, you can perform curl requests and store the curl-response for later use in your code. For instance:

Dynamic Internal Documentation
The API comes dynamically documented based on your existing editables!
Code: Select all
<cms:call 'curl'
  url="<cms:link masterpage='api.php' />?q=v0/people/doc/"
  method='get'
  into='my-curl-response'
/>

<cms:show my-curl-response as_json='1' />

// Outputs something along the following:
{
  "success": true,
  "message": "Viewing 'People' documentation.",
  "route": "v0.doc",
  "data": {
    "id": "3",
    "name": "people.php",
    "title": "People",
    "basename": "people",
    "fields": [
      {
        "id": "1",
        "template_id": "3",
        "name": "first_name",
        "label": "First name",
        "required": "1",
        "type": "text"
      },
      {
        "id": "2",
        "template_id": "3",
        "name": "last_name",
        "label": "Last name",
        "required": "1",
        "type": "text",
        "order": "1"
      }
    ]
  }
}



- - - - -



<cms:call 'curl'
  url="<cms:link masterpage='api.php' />?q=v0/index/"
  method='get'
  into='api-docs'
/>

<cms:show api-docs as_json='1' />


// Outputs
{
  "success": true,
  "message": "Listing API routes.",
  "route": "v0.index",
  "data": {
    "usage": {
      "synopsis": "The API is REST inspired and uses GET and POST HTTP verbs to perform CRUD operations on records within the system. All requests must be made via HTTPS. All responses are in JSON format.",
      "keyConcepts": {
        "templates": {
          "synopsis": "Templates are representations of record types. Each template has its own set of system fields and custom fields. For instance, the People template has (1) system fields of k_page_title, k_page_date, and k_page_name and (2) custom fields of first_name and last_name whereas the Blog Posts template has (1) system fields of k_page_title, k_page_date, and k_page_name and (2) custom fields of content, tags, and author.",
          "examples": [
            {
              "name": "blog-posts.php",
              "title": "Blog Posts",
              "exampleEnpoint": "https://your-site.com/api.php?q=v0/blog-posts/edit/&id=242"
            },
            {
              "name": "products.php",
              "title": "Products",
              "exampleEnpoint": "https://your-site.com/api.php?q=v0/products/&limit=40"
            },
            {
              "name": "people.php",
              "title": "People",
              "exampleEnpoint": "https://your-site.com/api.php?q=v0/people/&where=first_name.eq.John|age.gt.18"
            },
            {
              "name": "comments",
              "title": "Comments",
              "exampleEnpoint": "https://your-site.com/api.php?q=v0/comments/delete/&id=212833"
            }
          ]
        },
        "systemFields": {
          "synopsis": "System fields are fields that are required and optionally automatically created for each record. Each record has a 'k_page_title' (the title), 'k_page_name' (can be a random string or lowercase hyphenated version of the title), and 'k_page_date' (the publish date in Y-m-d H:i:s format). We can declare our own values here or use the default values.",
          "examples": [
            {
              "name": "k_page_title",
              "type": "text",
              "example": "John Doe",
              "default": "random_string"
            },
            {
              "name": "k_page_name",
              "type": "text",
              "example": "john-doe",
              "default": "random_string"
            },
            {
              "name": "k_page_date",
              "type": "datetime",
              "example": "2017-01-01 00:00:00",
              "default": "current_datetime"
            }
          ]
        },
        "customFields": {
          "synopsis": "Each record has custom fields defined by the template. These fields are documented in the 'fields' array in the template's v0.doc endpoint. Custom fields have no default value.",
          "examples": [
            {
              "name": "content",
              "type": "richtext",
              "example": "<h2>About our company</h2><p>Our company delivers innovative solutions and exceptional products/services to meet client needs. With a dedicated team, we prioritize customer satisfaction, building strong relationships based on trust. Our passion for innovation drives positive market impact.</p>"
            },
            {
              "name": "tags",
              "type": "relation",
              "template": "tags",
              "example": "8322,3882,1434"
            },
            {
              "name": "author",
              "type": "relation",
              "template": "people",
              "example": "7234"
            }
          ]
        },
        "records": {
          "synopsis": "Records are instances of templates. For instance, a record of the People template might be John Doe with k_page_title of 'John Doe', a first_name of 'John', and a last_name of 'Doe'. A record of the Blog Posts template might be a blog post with k_page_title of 'About our company', content of '<p>This is a post about our company.</p>', and tags of '242,288'."
        },
        "endpoints": {
          "synopsis": "Endpoints are the URLs that are used to interact with the API. There are four endpoints: v0.index (this one), v0.doc, v0.list, v0.create, v0.edit, and v0.delete. Endoints are documented below.",
          "actions": {
            "doc": {
              "synopsis": "Here, we document the fields of each template. For instance, if we wanted to get the documentation for the Blog Posts template, we would use the following endpoint: https://your-site.com/api.php?q=v0/blog-posts/doc/.",
              "examples": [
                {
                  "url": "https://your-site.com/api.php?q=v0/blog-posts/doc/",
                  "description": "Document the blog-posts.php template."
                },
                {
                  "url": "https://your-site.com/api.php?q=v0/people/doc/",
                  "description": "Document the people.php template."
                }
              ]
            },
            "list": {
              "synopsis": "Here, we can list records of a given template. For instance, if we wanted to get all blog posts, we would use the following endpoint: https://your-site.com/api.php?q=v0/blog-posts/list/. Multiple records can be listed by separating them with a comma (','). List actions can be combined with the 'limit' and 'offset' parameters to paginate results.",
              "examples": [
                {
                  "url": "https://your-site.com/api.php?q=v0/blog-posts/doc/",
                  "description": "List the first 10 blog posts."
                },
                {
                  "url": "https://your-site.com/api.php?q=v0/blog-posts/doc/&limit=40",
                  "description": "List the first 40 blog posts."
                },
                {
                  "url": "https://your-site.com/api.php?q=v0/blog-posts/doc/&limit=40&offset=40",
                  "description": "List the next set of 40 blog posts."
                },
                {
                  "url": "https://your-site.com/api.php?q=v0/blog-posts/doc/&id=243",
                  "description": "List the blog post with ID 243."
                },
                {
                  "url": "https://your-site.com/api.php?q=v0/blog-posts/doc/&id=243,244,245",
                  "description": "List the blog posts with ID 243, 244, and 245."
                },
                {
                  "url": "https://your-site.com/api.php?q=v0/blog-posts/doc/&where=tags.eq.242",
                  "description": "List all blog posts with the tag '242'."
                },
                {
                  "url": "https://your-site.com/api.php?q=v0/blog-posts/doc/&where=tags.eq.242&limit=40",
                  "description": "List the blog posts with the tag '242', but limit to the first 40."
                }
              ]
            },
            "create": {
              "synopsis": "Here, we can create a new record of a given template. For instance, if we wanted to create a new blog post, we would use the following endpoint: https://your-site.com/api.php?q=v0/blog-posts/create/. Note that create endoint requires POST values and won't accept GET parameters. For instance, if we wanted to create a new blog post with the title 'About our company', this following endpoint would not work: https://your-site.com/api.php?q=v0/blog-posts/create/&k_page_title=About%20our%20company. Instead, POST a form-urlencoded string either via JS fetch, curl, or similar HTTP tool.",
              "examples": [
                {
                  "url": "https://your-site.com/api.php?q=v0/blog-posts/create/",
                  "description": "Create a new blog post with title 'About our Company' and related tags '242,288'.",
                  "instructions": {
                    "viaCurl": "curl -s -d 'k_page_title=About%20our%20company&tags=242,288' -d api_key=$my-api-key https://your-site.com/api.php?q=v0/blog-posts/create/",
                    "viaForm": "Set up an HTML form with fields named 'k_page_title and 'tags', and submit a POST request to https://your-site.com/api.php?q=v0/blog-posts/create/."
                  }
                },
                {
                  "description": "Create a new person with the name 'Julie Stewart'.",
                  "url": "https://your-site.com/api.php?q=v0/blog-posts/create/&k_page_title=Julie%20Stewart&first_name=Julie&last_name=Stewart",
                  "instructions": {
                    "viaCurl": "curl -s -d 'k_page_title=Julie%20Stewart&first_name=Julie&last_name=Stewart' -d api_key=$my-api-key https://your-site.com/api.php?q=v0/people/create/",
                    "viaForm": "Set up an HTML form with fields named 'k_page_title', 'first_name', and 'last_name', and submit a POST request to https://your-site.com/api.php?q=v0/people/create/."
                  }
                }
              ]
            },
            "edit": {
              "synopsis": "Here, we can edit a given record of a given template. For instance, if we wanted to edit the blog post with ID 243, we would use the following endpoint: https://your-site.com/api.php?q=v0/blog-posts/edit/&id=243. Note that edit endoint requires POST values and won't accept GET parameters outside of the record's ID. For instance, if we wanted to edit the blog post with ID 243 with the title 'About our company', this following endpoint would not work: https://your-site.com/api.php?q=v0/blog-posts/edit/&id=243&k_page_title=About%20our%20company. Instead, POST a form-urlencoded string either via JS fetch, curl, or similar HTTP tool.",
              "examples": [
                {
                  "url": "https://your-site.com/api.php?q=v0/blog-posts/edit/&id=243",
                  "description": "Edit the blog post with ID 243 with title 'About our Company'.",
                  "instructions": {
                    "viaCurl": "curl -s -d 'k_page_title=About%20our%20company' -d api_key=$my-api-key https://your-site.com/api.php?q=v0/blog-posts/edit/&id=243",
                    "viaForm": "Set up an HTML form with a field named 'k_page_title', and submit a POST request to https://your-site.com/api.php?q=v0/blog-posts/edit/&id=243."
                  }
                },
                {
                  "description": "Edit the person with ID 243 with the name 'Julie Stewart'.",
                  "url": "https://your-site.com/api.php?q=v0/people/edit/&id=243&k_page_title=Julie%20Stewart&first_name=Julie&last_name=Stewart",
                  "instructions": {
                    "viaCurl": "curl -s -d 'k_page_title=Julie%20Stewart&first_name=Julie&last_name=Stewart' -d api_key=$my-api-key https://your-site.com/api.php?q=v0/people/edit/&id=243",
                    "viaForm": "Set up an HTML form with fields named 'k_page_title', 'first_name', and 'last_name', and submit a POST request to https://your-site.com/api.php?q=v0/people/edit/&id=243."
                  }
                }
              ]
            },
            "delete": {
              "synopsis": [
               
              ]
            }
          },
          "filters": {
            "synopsis": "Filters are used to filter the results of a request. For instance, if we wanted to get all blog posts with the tag '242', we would use the following endpoint: https://your-site.com/api.php?q=v0/blog-posts/&where=tags.eq.242. Multiple filters can be used by separating them with a pipe character ('|'). For instance, if we wanted to get all blog posts with the tag '242' and the tag '288', we would use the following endpoint: https://your-site.com/api.php?q=v0/blog-posts/&where=tags.eq.242|tags.eq.288. Filters can be combined with the 'limit' and 'offset' parameters to paginate results. For instance, if we wanted to get the 10th through 20th blog posts with the tag '242' and the tag '288', we would use the following endpoint: https://your-site.com/api.php?q=v0/blog-posts/&where=tags.eq.242|tags.eq.288&limit=10&offset=10.",
            "examples": [
              {
                "name": "where",
                "type": "string",
                "example": "tags.eq.242"
              },
              {
                "name": "limit",
                "type": "integer",
                "example": "10"
              },
              {
                "name": "offset",
                "type": "integer",
                "example": "10"
              }
            ]
          }
        }
      }
    },
    "endpoints": [
      {
        "name": "v0.index",
        "path": "v0/index/"
      },
      {
        "name": "v0.templates",
        "path": "v0/templates/"
      },
      {
        "name": "v0.list",
        "path": "v0/{:template}/"
      },
      {
        "name": "v0.doc",
        "path": "v0/{:template}/doc/"
      },
      {
        "name": "v0.create",
        "path": "v0/{:template}/create/"
      },
      {
        "name": "v0.edit",
        "path": "v0/{:template}/edit/"
      },
      {
        "name": "v0.delete",
        "path": "v0/{:template}/delete/"
      }
    ]
  }
}