@flotiq/flotiq-api-sdk
v1.0.0-alpha.12
Published
Dedicated js/ts client for Flotiq API which focuses on type safety and IDE autocompletion of user data types.
Readme
Flotiq API Javascript/Typescript SDK
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:
Features
| | 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.instanceOfhelper
Installation from npm
- run
npm install @flotiq/flotiq-api-sdk - (optional) Generate typescript types for your data definitions
- Create
.envfile and addFLOTIQ_API_KEYenv variable inside - Run
npm exec flotiq-api-typegento generate account type definitions
- Create
Installation from yarn
- run
yarn add @flotiq/flotiq-api-sdk - (optional) Generate typescript types for your data definitions
- Create
.envfile and addFLOTIQ_API_KEYenv variable inside - Run
yarn exec flotiq-api-typegento generate account type definitions
Usage examples

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));Batch create objects
api.content.blogpost
.batchCreate([
{title: "New post 2"},
{title: "New post 3"}
])
.then(({ batch_success_count }) => console.log(batch_success_count));Batch delete objects
api.content.blogpost
.batchDelete(["post-1", "post-2"])
.then(({ deletedCount }) => console.log(deletedCount));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",
},
},
};
},
},
],
});SDK API - Flotiq class
constructor
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_KEYwill 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) andfetchmethod 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; }> }
api.content.<content_definition_api_name>
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.listOptionscan contain following properties:hydrate: 0 | 1 | 2- hydration level for relations.0will not include related objects,1will join data from related objects,2will join data from related objects and from their relations. Default value is0.filters: Object- object containing filtering rulesorderBy: string- which Content Type Definition property determines the orderorderDirection: "asc" | "desc"- order directionlimit: number- how many results should be returned in the resultpage: number- page number. Withlimit: 10,page: 1will return first 10 results,page: 2- results11-20and so on.
get(id: string, options)- find single object by its id.optionsis an object with following properties:hydrate: 0 | 1 | 2- hydration level (see above)
update(id: string, data: Object)- PerformPUToperation on Content Type Object with provided data.patch(id: string, data: Object)- PerformPATCHoperation on Content Type Object with partial data.delete(id: string)- remove single object.create(data: Object)- create new objectsearch(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' })
api.search.query(searchParams)
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 resultsorderDirection: "asc" | "desc"- ordering directionlimit: number- number of results per pagepage: number- page numberq: string- query string (required)contentType: string[]- Limits results to provided content types. If not provided, Search API will search through all Content Type Definitions.
api.helpers
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.optionscan 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 imagetype: '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.
api.helpers.hydrate(contentObject, options)- virtually hydrate content object with its relations. This method allows you to hydrate objects after they have been fetched, which is useful when you need different hydration levels for different use cases or when you need to hydrate only selected types.optionscan contain:level?: number- hydration level, determines how deep the relations should be hydratedfailMode?: "throw" | "insertNull" | "ignore"- how to handle failed hydration attempts - default:throwthrow- if any relation is not found, throw an errorinsertNull- replace data link object withnullif relation was not foundignore- leave data link object if relation was not found
hydratedTypes?: string[]- array of content type names to hydrate (if not provided, all relations will be hydrated)
Example:
const blogPost = await api.content.blogPost.get("id"); const blogPostHydratedTwice = await api.helpers.hydrate(blogPost, { failMode: "insertNull", hydratedTypes: ["category"], level: 2, }); if (api.helpers.instanceOf(blogPostHydratedTwice.category, "category")) { // category is now typed as Category console.log(blogPostHydratedTwice.category.name); }
flotiq-api-typegen
flotiq-api-typegen
Generates types for Flotiq API based on the content type definitions on your account
Options
| 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 |
Development
See CONTRIBUTING.md
Development Status
Features that might appear in the future (not necessarily before v1):
JS flavour support
- [x] Typescript -
tsfiles - [x] CommonJS -
jswithrequire - [ ] ECMAScript -
jswithimport(esm build needs to be added, for now it may not work in all projects)
- [x] Typescript -
[x] typegen based on CTDs
- [x]
filterknows what filter flotiq have - [x] (partial)
filterknows what fields user have (were missing internals for the moment, but own fields are listed) - [x]
hydrateknows what are results for different hydration levels - [x] crud operations know param and result types
- [x]
filterknows it can onlylist - [x]
hydrateknows it can onlylistandget
- [x]
[x] basic get/list/update/create/delete objects
[x] filter
- [x] smart filter types that know if they require
filterandfilter2params
- [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)
[x] 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
.envwhen 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 backendexample data migration
example - cross space usage (e.g. next implementation of https://flotiq.com/blog/)
