Dedicated js/ts client for Flotiq Headless CMS API which focuses on type safety and IDE autocompletion of user data types.
With types generated using our typegen command, it enables fast and typesafe development with Flotiq as a data backend.
Generated flotiq-api.d.ts
types can be either committed with your code, or .gitignore
-d and generated during development and CI/CD.
You can still use all the API features without type generation. TypeScript user types can be added or removed at any point in development without code changes required.
Note: This SDK is stable and actively maintained. It is production-ready as of version 1.0.0.
New features and improvements are still planned — see Development Status for more.
Table of contents:
JavaScript | Typescript | |
---|---|---|
Fetch and update Flotiq content | ✅ | ✅ |
Search API | ✅ | ✅ |
TS/JS types for content definitions in data | ✅* | ✅** |
TS/JS types for content definitions in data with hydrated relations | ✅* | ✅** |
TS/JS types for content definitions in filters | ✅* | ✅** |
Discriminating between data types in any type relations and search responses | ✅*** | ✅*** |
Custom Middleware (e.g. to add next cache to each fetch ) |
✅ | ✅ |
Generating URL to uploaded Media | ✅ | ✅ |
CTD operations | ❌ | ❌ |
- * Requires typegen types and
<reference path="./flotiq-api.d.ts" />
annotation - ** Requires typegen types
- *** Requires typegen types, only with
api.helpers.instanceOf
helper
- run
npm install @flotiq/flotiq-api-sdk
- (optional) Generate typescript types for your data definitions
- Create
.env
file and addFLOTIQ_API_KEY
env variable inside - Run
npm exec flotiq-api-typegen
to generate account type definitions
- Create
- run
yarn add @flotiq/flotiq-api-sdk
- (optional) Generate typescript types for your data definitions
- Create
.env
file and addFLOTIQ_API_KEY
env variable inside - Run
yarn exec flotiq-api-typegen
to generate account type definitions
In JS (CommonJS)
// Only if types were generated:
/// <reference path="./flotiq-api.d.ts" />
const { Flotiq } = require("@flotiq/flotiq-api-sdk");
const api = new Flotiq(); // read api key automatically from process.env.FLOTIQ_API_KEY
// or
const api = new Flotiq({
apiKey: "<YOUR API KEY>",
});
await api.content._media.list().then((response) => {
console.log("media > list", response);
});
In Typescript
import { Flotiq } from "@flotiq/flotiq-api-sdk";
const api = new Flotiq(); // read api key automatically from process.env.FLOTIQ_API_KEY
// or
const api = new Flotiq({
apiKey: "<YOUR API KEY>",
});
await api.content._media.list().then((response) => {
console.log("media > list", response);
});
List objects
api.content._media.list().then((response) => {
console.log("media > list", response);
});
api.content._media
.list({
page: 1,
limit: 100,
orderBy: "fileName",
orderDirection: "asc",
})
.then((response) => {
console.log(
"media > list (limit=100, page=1, orderBy=fileName,asc)",
response
);
});
Get object
api.content._media.get("_media-xyz-id").then((response) => {
console.log("media > get by id", response);
});
Hydrate objects
// works also on list()
api.content._media.get("_media-xyz-id", { hydrate: 1 }).then((response) => {
console.log("media > get", response.tags);
});
Hydrate objects to second level
// works also on list()
api.content._media.get("_media-xyz-id", { hydrate: 2 }).then((response) => {
console.log("media > get", response.tags);
});
Generate media url
// works also on list()
api.content._media.get("_media-xyz-id").then((response) => {
console.log(
"media > url",
api.helpers.getMediaUrl(response, {
width: 200,
height: 200,
})
);
});
Filter Objects
api.content.blogpost
.list({
filters: {
image: {
type: "contains",
filter: "_media",
},
},
})
.then((response) => {
console.log("only blogposts with image", response);
});
Create objects
api.content.blogpost
.create({
title: "New post 2",
})
.then((newPost) => console.log(newPost.id));
Iterate all pages
const params = {
page: 1,
limit: 100,
orderBy: "fileName" as keyof InternalMedia,
orderDirection: "asc" as "asc" | "desc",
};
let result: ListResponse<InternalMedia>;
do {
result = await api.content._media.list(params);
console.log(result.data[0].fileName);
params.page++;
} while (result.total_pages > result.current_page);
Search API
api.search.query({ q: "asd" }).then((response) => {
console.log(
"search score for item 0",
response.data[0].score,
response.data[0].item.id
);
});
Search API with type checking
api.search
.query({ q: "The name of the thing" })
// note added response type below:
.then((response: SearchResponse<Blogpost | Product>) => {
for (const searchResult of response.data) {
if (api.helpers.instanceOf(searchResult.item, "blogpost")) {
// firstResult.item is now Blogpost
console.log("Found blogpost!", searchResult.item.title);
} else if (api.helpers.instanceOf(searchResult.item, "product")) {
// firstResult.item is now Product
console.log("Found product!", searchResult.item.name);
} else {
// firstResult.item is neither Blogpost or Product
// do nothing
}
}
});
CTD Scoped Search API
api.content.blogpost.search({ q: "Product updates" }).then((response) => {
const blogpost = response.data[0].item; // Typed as Blogpost!
console.log("Typed search result", blogpost.title);
});
Middleware Intercepting all requests that go to flotiq api:
const api = new Flotiq({
apiKey: "<YOUR API KEY>",
middleware: [
{
beforeRequest: async (requestContext) => {
console.log("Requesting:", requestContext.url);
return {
...requestContext,
init: {
...requestContext.init,
headers: {
...requestContext.init.headers,
"User-Agent": "My cool app",
},
},
};
},
},
],
});
import { Flotiq } from "@flotiq/flotiq-api-sdk";
const api = new Flotiq();
//or
const api = new Flotiq(options);
options
argument can contain the following properties:
-
apiKey: string
- your Flotiq api key. If none provided,process.env.FLOTIQ_API_KEY
will be used. -
apiUrl: string
- Flotiq API url. By default, it points tohttps://api.flotiq.com
. You'll need to change it only if you are Flotiq Enterprise client, with self-hosted Flotiq instance. -
middleware: Array
- an array of middleware objects intercepting all requests. If multiple middlewares are used, they will be executed in order provided in the array.-
middleware[].beforeRequest
- a callback intercepting each request before it happens. It can override all fetch options (including url) andfetch
method itself. It must return new (or modified) context.
{ beforeRequest: (requestContext: { url: string; init: RequestInit; fetch: typeof fetch; }) => Promise<{ url: string; init: RequestInit; fetch: typeof fetch; }> }
-
You can access each content type data api using its api name.
Content api provides following methods:
-
list(listOptions)
- used to list mulitple objects. For more infe see Flotiq Docs - Listing content through the API.listOptions
can contain following properties:-
hydrate: 0 | 1 | 2
- hydration level for relations.0
will not include related objects,1
will join data from related objects,2
will join data from related objects and from their relations. Default value is0
. -
filters: Object
- object containing filtering rules -
orderBy: string
- which Content Type Definition property determines the order -
orderDirection: "asc" | "desc"
- order direction -
limit: number
- how many results should be returned in the result -
page: number
- page number. Withlimit: 10
,page: 1
will return first 10 results,page: 2
- results11-20
and so on.
-
-
get(id: string, options)
- find single object by its id.options
is an object with following properties:-
hydrate: 0 | 1 | 2
- hydration level (see above)
-
-
update(id: string, data: Object)
- PerformPUT
operation on Content Type Object with provided data. -
patch(id: string, data: Object)
- PerformPATCH
operation on Content Type Object with partial data. -
delete(id: string)
- remove single object. -
create(data: Object)
- create new object -
search(searchParams)
- search through all objects of given type with Full-text search API endpoint. (see detailed parameters below)Results are scoped to selected Content Type Definition, therefore the two following search queries are performing the same API call:
api.content.<content_definition_api_name>.search({ q: 'search string' })
api.search.query({ contentType: ["<content_definition_api_name>"], q: 'search string' })
Performs Full-text search operation with provided parameters. For more details see Flotiq docs - Search API.
searchParams
accepts the following parameters:
-
orderBy: string
- which field determines order of results -
orderDirection: "asc" | "desc"
- ordering direction -
limit: number
- number of results per page -
page: number
- page number -
q: string
- query string (required) -
contentType: string[]
- Limits results to provided content types. If not provided, Search API will search through all Content Type Definitions.
Helper methods to work with API and SDK itself. These are:
-
api.helpers.getMediaUrl(media: InternalMedia, options)
- generate url to media file uploaded to Flotiq.options
can contain:-
width: number
,height: number
- for images, Flotiq will return resized image. If one of the dimensions is omitted, resize operation will preserve original image proportions. -
variantName: string
- for images, Flotiq will return variant of the image. Can be also resized. If variant is not present url fallbacks to original image -
type: 'image'| 'file'
- default: 'image', use it to change the url behaviour and return urls with '/file/' instead of '/image/widthxheight/'. -
omitFileName: boolean
- default: false, use it to change the url behaviour and return urls with '/ID.extension' instead of '/ID/fileName'.
-
-
api.helpers.instanceOf(data: object, ctdName: string)
- checks if object belongs to provided CTD.When using generated Typescript types, it will determine which Content Type Definition interface is the object typed with. See Search API with type checking example for more detailed use case. It will also help determine hydrated relation data type.
flotiq-api-typegen
Generates types for Flotiq API based on the content type definitions on your account
Option | Description | Default |
---|---|---|
--flotiq-key |
Flotiq API key with read only access | |
--flotiq-api-url |
Url to the Flotiq API | https://api.flotiq.com |
--output |
Path to target filename | ./flotiq-api.d.ts |
--watch |
Watch for changes in the content type definitions and regenerate types | false |
See CONTRIBUTING.md
Features that might appear in the future (not necessarily before v1):
-
JS flavour support
- [x] Typescript -
ts
files - [x] CommonJS -
js
withrequire
- [ ] ECMAScript -
js
withimport
(esm build needs to be added, for now it may not work in all projects)
- [x] Typescript -
-
[x] typegen based on CTDs
- [x]
filter
knows what filter flotiq have - [x] (partial)
filter
knows what fields user have (were missing internals for the moment, but own fields are listed) - [x]
hydrate
knows what are results for different hydration levels - [x] crud operations know param and result types
- [x]
filter
knows it can onlylist
- [x]
hydrate
knows it can onlylist
andget
- [x]
-
[x] basic get/list/update/create/delete objects
-
[x] filter
- [x] smart filter types that know if they require
filter
andfilter2
params
- [x] smart filter types that know if they require
-
[x] hydration
- [ ] default hydration:
export const flotiq = new Flotiq({ hydrate: 1 })
- [ ] default hydration:
-
[ ] search (wip)
- [x] content type scoped search
- [ ] all search params
- [x] result type recognition
-
[ ] virtual hydration (e.g. after search)
-
[ ] batch operations
-
[x] ordering
-
[x] pagination parameters
-
[x] middlewares (e.g. for next)
- [x] fetch override
-
[ ] CTD operations (list/create/get/update) -
flotiq.definitions.list()
etc -
[x] api key autoloading from
.env
when creatingFlotiq
-
[x]
getMediaUrl
-
[ ] upload media api
-
[x] changelog
-
[ ] contributing guide (in progress)
-
[x] CLI params, help text and other DX features (
yargs
) -
[ ] Namespace support for multiple spaces used
-
proposed api:
const defaultApi = new Flotiq(); // Based on key provided as FLOTIQ_API_KEY const blogApi = new Flotiq.ns.Blog(); // Based on space name or provided alias && FLOTIQ_BLOG_API_KEY const pageApi = new Flotiq.ns.Page(); // Based on space name or provided alias && FLOTIQ_PAGE_API_KEY blogApi.content.blogpost... // typed ctds from blog space pageApi.content.menu... // Typed ctds from page space pageApi.content.blogpost... // Not existing ctds in given space will not provide typing and throw an error 404 from backend
-
example data migration
-
example - cross space usage (e.g. next implementation of https://flotiq.com/blog/)
-