{"id":15459184,"url":"https://github.com/luchiniatwork/contentql","last_synced_at":"2025-04-22T10:37:32.220Z","repository":{"id":62433449,"uuid":"102002516","full_name":"luchiniatwork/contentql","owner":"luchiniatwork","description":"Access Contentful data using Om Next Queries","archived":false,"fork":false,"pushed_at":"2019-04-25T21:50:33.000Z","size":42,"stargazers_count":15,"open_issues_count":3,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-22T10:37:24.797Z","etag":null,"topics":["clojure","clojurescript","cms","content","contentful","omnext","query-language"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/luchiniatwork.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-08-31T13:16:37.000Z","updated_at":"2023-11-20T08:01:07.000Z","dependencies_parsed_at":"2022-11-01T21:01:51.550Z","dependency_job_id":null,"html_url":"https://github.com/luchiniatwork/contentql","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luchiniatwork%2Fcontentql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luchiniatwork%2Fcontentql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luchiniatwork%2Fcontentql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luchiniatwork%2Fcontentql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/luchiniatwork","download_url":"https://codeload.github.com/luchiniatwork/contentql/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250221916,"owners_count":21394786,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","clojurescript","cms","content","contentful","omnext","query-language"],"created_at":"2024-10-01T23:05:17.971Z","updated_at":"2025-04-22T10:37:32.161Z","avatar_url":"https://github.com/luchiniatwork.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ContentQL\n\nContentQL allows one to access Contentful data using Om Next Queries.\n\n[Contentful](https://www.contentful.com/) is a popular headless, cloud-based CMS system. Beyond\nbeing purely an API-first CMS system it also supports somewhat complex data schemas, responsive\nimages and webhooks making it a very compelling content platform.\n\nDespite the great features provided by Contentful, one thing remains somewhat challenging:\nits API is not the most straightforward to use.\n\nInstead of porting Contentful's API on a one-on-one basis to Clojure and ClojureScript, this\nlibrary takes an abstraction route to querying: it uses Om Next's Query language as its main\ninterface.\n\n## Table of Contents\n\n* [Getting Started](#getting-started)\n* [Motivation](#motivation)\n* [Features](#features)\n* [Query Syntax](#query-syntax)\n* [Usage](#usage)\n* [Pagination](#pagination)\n* [Bugs](#bugs)\n* [Help!](#help)\n\n## Getting Started\n\nAdd the following dependency to your `project.clj` file:\n\n[![Clojars Project](http://clojars.org/luchiniatwork/contentql/latest-version.svg)](http://clojars.org/luchiniatwork/contentql)\n\n## Motivation\n\nBy using Om Next queries one can:\n\n* easily describe deep nested joins\n* clearly parameterize root (as well as other) queries\n* easily express field selections\n* easily dispatch queries to Contentful from Om Next remotes\n* easy-to-use responsive images as part of the query\n* describe your queries using macro syntax\n\n## Features\n\nContentQL's key features are:\n\n1. Support for both Clojure and ClojureScript\n2. Seamless `clojure.core.async` and `cljs.core.async` support\n3. Use with or without Om Next - it's your call\n4. Access to deep field selectors\n5. Access to filters, ordering, and pagination\n6. Dead simple, cached, responsive images\n\n## Query Syntax\n\nThe Om Next query syntax is beautifuly described by António Monteiro [here](https://anmonteiro.com/2016/01/om-next-query-syntax/) but if you need a quick primer, here it is:\n\n### Simple properties\n\nIf you have a content type called `blogs`, you can query its entries with:\n\n```clojure\n'[:blogs]\n```\n\nYou can always combine several content types in one go:\n\n```clojure\n'[:blogs :articles]\n```\n\n### Joins\n\nIf you want just the `title` and the `body` of your `blogs`, you can use a join such as:\n\n```clojure\n'[{:blogs [:title :body]}]\n```\n\n### Nested joins\n\nAssuming your `blogs` content type has an `author` embedded whose `name` you want to fetch\nas well, simply nest your joins:\n\n```clojure\n'[{:blogs [:title :body\n           {:author [:name]}]}]\n```\n\nThis will continue to give you the `title` and the `body` of each blog entry but now also\nthe name of each blog's author.\n\n### Parametrized queries\n\nQueries can be parametrized by using a list where the second element is a map of parameters.\nIf you want the blog identified by `id` `\"3x1YMtJ1CoOWk0ycYsOw4I\"` you can fetch it with:\n\n```clojure\n'[(:blogs {:id \"3x1YMtJ1CoOWk0ycYsOw4I\"})]\n```\n\nThis can be combined with joins, ie:\n\n```clojure\n'[({:blogs [:title :body]} {:id \"3x1YMtJ1CoOWk0ycYsOw4I\"})]\n```\n\nOr even nested joins:\n\n```clojure\n'[({:blogs [:title :body\n            {:author [:name]}]}\n   {:id \"3x1YMtJ1CoOWk0ycYsOw4I\"})]\n```\n### Supported parameters for Query roots\n\nAll your content types can be queries as part of a query root.\n\nYou've already seen above how to query a specific entry by its `id`:\n\n```clojure\n'[(:blogs {:id \"3x1YMtJ1CoOWk0ycYsOw4I\"})]\n```\n\nIn addition to `:id` the other supported query root parameters are:\n\n* `:limit` - limits the page size to its value (i.e. `:limit 10`)\n* `:skip` - skips the specified number of entries (i.e. `:skip 5` skips 5 entries)\n* `:order` - allows ordering of the entries (i.e. `:order \"fields.name\"` ordres the dataset by `name`). Reverse ordering is supported with the addition of `-` (i.e. `:order \"-fields.name\"`)\n\n### Supported parameters for Images\n\nAny image asset is immidetally wrapped in an image entity containing three fields `:width`, `:height` and `:url`. The asset can be scaled up or down by sending the intended `:width` or `:height` parameter.\n\nIn order to see it in action, suppose your `author` has an `avatar` image and you want it constrained within a `width` of 150 pixels:\n\n```clojure\n'[{:authors [({:avatar [:width\n                        :height\n                        :url]}\n              {:width 150})]}]\n```\n\n## Usage\n\nAsync channels are slightly different between Clojure and ClojureScript due to underlying characteristics of how the JVM and the JavaScript environment deal with multi-threading. ContentQL supports both platforms seamlessly.\n\nFor Clojure, require `contentql.core` and `clojure.core.async`:\n\n```clojure\n(ns my-project\n  (:require [contentql.core :as contentql]\n            [clojure.core.async :refer [go \u003c!]]))\n```\n\nFor ClojureScript, require `contentql.core` and `cljs.core.async`:\n\n```clojure\n(ns my-project\n  (:require-macros [cljs.core.async.macros :refer [go]])\n  (:require [contentql.core :as contentql]\n            [cljs.core.async :refer [\u003c!]]))\n```\n\nThen create a Contentful connection with `contentql/create-connection`. It receives four fields `:space-id`, `:access-token`, `:mode` and `:environment`. Use the space id and access token found on your Contentful dashboard. `:mode` should be either `:live` (for production environment) or `:preview` for (guess what, preview mode). Lastly, `:environment` should be the environment you want to fetch data for.\n\n```clojure\n(def config {:space-id \"c3tshf2weg8y\"\n             :access-token \"e87aea51cfd9193df88f5a1d1b842d9a43cc4f2b02366b7c0ead54fb1b0ad6d4\"\n             :mode :live\n             :environment \"master\"})\n\n(def conn (contentql/create-connection config))\n```\n\nOnce your connection is created, send it to `contentql/query` with your query along:\n\n```clojure\n(go \n  (let [res (\u003c! (contentql/query conn '[{:blogs [:id :title]}]))]\n    (println res)))\n```\n\n`contentql/query` returns an async channel. The snippet above uses a `go` block to send the code to a separate thread. If you are in Clojure and want a blocking alternative, simply import `\u003c!!` from `clojure.core.async` and use the returned channel like this:\n\n```clojure\n(\u003c!! (contentql/query conn '[{:blogs [:id :title]}]))\n```\n\n## Pagination\n\nPagination is supported by default on all query root (all your content types). Your response\nwill be wrapped in a map containing a `:nodes` field and an `:info` field. The former will\nencapsulate the entries for the current page while info gives you some metadata about the\ntotal universe of entries and the page you are in. This is a typical `:info` map:\n\n```clojure\n{:nodes {:total 33}\n :page {:size 4\n        :current 1\n        :total 9\n        :has-next? true\n        :has-prev? false}\n :pagination {:cursor 0\n              :next-skip 4\n              :prev-skip 0}}\n```\n\nThis tells us that there are `33` total nodes in the dataset while the page size is `4`.\nThis payload represents page `1` of a total of `9` pages. There are a next page from where we\nare but no previous page. The starting cursor for the page is entry `0` (the very first one)\nand to get to the next page we need to skip `4` entries.\n\nPagination is achieved by manipulating the parameters `:limit` (to specify page size) and\n`:skip` (to specify how many entries to skip). These two parameters can be sent to any query\nroot.\n\n## Content Type, Type Name and ID\n\nQueries made to ContentQL will include a `content-type`, `type-name` and\n`id` field by default. `content-type` can be found in any asset that is \nreturned from Contentful, and will contain the asset type and extension. \n`type-name` will return the content model of a field and will contain the \ncontent type ID. Lastly, the `id` field contains the entry ID that can be \nused to identify a specific content.\n\n## Bugs\n\nIf you find a bug, submit a [Github issue](https://github.com/luchiniatwork/contentql/issues).\n\n## Help\n\nThis project is looking for team members who can help this project succeed!\nIf you are interested in becoming a team member please open an issue.\n\n## License\n\nCopyright © 2017 Tiago Luchini\n\nDistributed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluchiniatwork%2Fcontentql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fluchiniatwork%2Fcontentql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluchiniatwork%2Fcontentql/lists"}