16 KiB
JavaScript SDK
The JS SDK provides an intuitive interface for the Directus API from within a JavaScript-powered project (browsers and node.js). The default implementation uses Axios for transport and
localStoragefor storing state.
Installation
npm install @directus/sdk
Usage
import { Directus } from '@directus/sdk';
const directus = new Directus('https://api.example.com/');
NOTE All methods return promises. Make sure to await methods, for example:
import { Directus } from '@directus/sdk';
const directus = new Directus('https://api.example.com/');
// Wait for login to be done...
await directus.auth.login({
email: 'admin@example.com',
password: 'password',
});
// ... before fetching items
const articles = await directus.items('articles').readMany();
console.log({
items: articles.data,
total: articles.meta.total_count,
});
Global
Initialize
import { Directus } from '@directus/sdk';
const directus = new Directus('https://api.example.com/');
url
The constructor accepts the URL as the first parameter.
options.auth
The authentication implementation. See Auth for more information.
Defaults to an instance Auth.
options.storage
The storage implementation. See Storage for more information.
Defaults to an instance of MemoryStorage when in node.js, and LocalStorage when in browsers.
NOTE:
If you plan to use multiple SDK instances at once, keep in mind that they will share the Storage across them, leading to unpredictable behaviors. This scenario might be a case while writing tests.
For example, the SDK instance that executed last the login() method writes the resulting access_token into the
Storage and overwrites any prior fetched access_token from any other SDK instance. That might mix up your test
scenario by granting false access rights to your previous logged-in users.
Adding prefixes to your Storage instances would solve this error:
import { Directus, MemoryStorage } from '@directus/sdk';
import { randomBytes } from 'crypto';
// ...
const prefix = randomBytes(8).toString('hex');
const storage = new MemoryStorage(prefix);
const url = `http://${host}:${port}`;
const directus = new Directus(url, { storage });
options.transport
The transport implementation. See Transport for more information.
Defaults to an instance of AxiosTransport.
Example
const directus = new Directus('http://api.example.com/');
Get / Set API URL
// Get the API base URL
console.log(directus.transport.url); // => https://api.example.com/
// Set the API base URL
directus.transport.url = 'https://api2.example.com';
Access to transport/Axios
You can tap into the transport through directus.transport. If you are using the (default) AxiosTransport, you can
access axios through directus.transport.axios.
Intercepting requests and responses
Axios transport offers a wrapper around Axios interceptors to make it easy for you to inject/eject interceptors.
const requestInterceptor = directus.transport.requests.intercept((config) => {
config.headers['My-Custom-Header'] = 'Header value';
return config;
});
// If you don't want the interceptor anymore, remove it
requestInterceptor.eject();
const responseInterceptor = directus.transport.responses.intercept((response) => {
console.log('Response received', { response });
return response;
});
// If you don't want the interceptor anymore, remove it
responseInterceptor.eject();
Items
You can get an instance of the item handler by providing the collection (and type, in the case of TypeScript) to the
items function. The following examples will use the Article type.
JavaScript
// import { Directus, ID } from '@directus/sdk';
const { Directus } = require('@directus/sdk');
const directus = new Directus('https://api.example.com');
const articles = directus.items('articles');
TypeScript
import { Directus, ID } from '@directus/sdk';
// Map your collection structure based on its fields.
type Article = {
id: ID;
title: string;
body: string;
published: boolean;
};
// Map your collections to its respective types. The SDK will
// infer its types based on usage later.
type MyBlog = {
// [collection_name]: typescript_type
articles: Article;
// You can also extend Directus collection. The naming has
// to match a Directus system collection and it will be merged
// into the system spec.
directus_users: {
bio: string;
};
};
// Let the SDK know about your collection types.
const directus = new Directus<MyBlog>('https://directus.myblog.com');
// typeof(article) is a partial "Article"
const article = await directus.items('articles').readOne(10);
// Error TS2322: "hello" is not assignable to type "boolean".
// post.published = 'hello';
Create Single Item
await articles.createOne({
title: 'My New Article',
});
Create Multiple Items
await articles.createMany([
{
title: 'My First Article',
},
{
title: 'My Second Article',
},
]);
Read All
await articles.readMany();
Read By Query
await articles.readMany({
search: 'Directus',
filter: {
date_published: {
_gte: '$NOW',
},
},
});
Read By Primary Key(s)
await articles.readOne(15);
Supports optional query:
await articles.readOne(15, { fields: ['title'] });
Update Multiple Items
await articles.updateMany([15, 42], {
title: 'Both articles now have the same title',
});
Supports optional query:
await articles.updateMany(
[15, 42],
{
title: 'Both articles now have the same title',
},
{
fields: ['title'],
}
);
Delete
// One
await articles.deleteOne(15);
// Multiple
await articles.deleteMany([15, 42]);
Activity
Read All Activity
await directus.activity.readMany();
Read Activity By Query
await directus.activity.readMany({
filter: {
action: {
_eq: 'create',
},
},
});
Read Activity By Primary Key(s)
await directus.activity.readOne(15);
Supports optional query:
await directus.activity.readOne(15, { fields: ['action'] });
Create a Comment
await directus.activity.comments.create({
collection: 'articles',
item: 15,
comment: 'Hello, world!',
});
Update a comment
await directus.activity.comments.update(31, {
comment: 'Howdy, world!',
});
Note: The passed key is the primary key of the comment
Delete a comment
await directus.activity.comments.delete(31);
Note: The passed key is the primary key of the comment
Auth
Configuration
Directus will accept custom implementations of the IAuth interface. The default implementation Auth can be imported
from @directus/sdk. The default implementation will require you to pass the transport and storage implementations. All
options are optional.
import { Auth } from '@directus/sdk';
// ...
const sdk = new Directus('url', {
auth: new Auth(transport, storage, options);
// ...
});
transport
The transport responsible for communicating with Directus backend.
Defaults to an instance of AxiosTransport when not creating Auth youself.
storage
The storage responsible for storing authentication and sdk state.
When not creating Auth youself, defaults to MemoryStorage in node.js, and LocalStorage in browsers.
options
options.mode
Accepts cookie or json.
When in cookie mode, the API will set the refresh token in an httpOnly secure cookie that can't be accessed from
client side JavaScript. This is the most secure way to connect to the API from a public front-end website.
When you can't rely on cookies, or need more control over handling the storage of the cookie (like in node.js), use
json mode. This will return the refresh token in the "normal" payload. The storage of these tokens are handled by the
storage implementation.
Defaults to cookie in browsers, json in node.js.
options.refresh
See Refresh auth token.
Get current token
const token = directus.auth.token;
Login
With credentials
await directus.auth.login({
email: 'admin@example.com',
password: 'd1r3ctu5',
});
With static tokens
await directus.auth.static('static_token');
Refresh auth token
You can set authentication to auto-refresh the token once it's close to expire.
await directus.auth.login(
{
email: 'admin@example.com',
password: 'd1r3ctu5',
},
{
refresh: {
auto: true,
},
}
);
You can also set how much time before the expiration you want to auto-refresh the token. When not specified, 30 sec is the default time.
await directus.auth.login(
{
email: 'admin@example.com',
password: 'd1r3ctu5',
},
{
refresh: {
auto: true,
time: 30000, // refesh the token 30 secs before the expiration
},
}
);
Refresh Auth Token
You can manually refresh the authentication token. This won't try to refresh the token if it's still valid in the eyes of the SDK.
Also worth mentioning that any concurrent refreshes (trying to refresh while a there's an existing refresh running), will result in only a single refresh hitting the server, and promises resolving/rejecting with the result from the first call. This depends on the implementation of IAuth you're using.
await directus.auth.refresh();
You can force the refresh by passing true in the first parameter.
An optional parameter will accept auto-refresh information.
await directus.auth.refresh(true);
This function can either return the AuthResult in case a refresh was made, false in case SDK thinks it's not needed,
or throw an error in case refresh fails.
Logout
await directus.auth.logout();
Request a Password Reset
await directus.auth.password.request('admin@example.com');
Reset a Password
await directus.auth.password.reset('abc.def.ghi', 'n3w-p455w0rd');
Note: The token passed in the first parameter is sent in an email to the user when using request()
Transport
The transport object abstracts how you communicate with Directus. Transports can be customized to use different HTTP libraries for example.
Interface
// Simplified version, `import { ITransport } from '@directus/sdk';`
interface ITransport {
url;
get(path);
head(path);
options(path);
delete(path, data = undefined);
post(path, data);
put(path, data);
patch(path, data);
}
AxiosTransport
The default transport used in both browser and node deployments. It supports auto refresh on request.
Options
AxiosTransport requires a base URL and a storage implementation to work.
const transport = new AxiosTransport('http://example.com', new MemoryStorage(), async () => {
await sdk.auth.refresh();
});
await transport.get('/server/info');
Storage
The storage is used to load and save SDK data.
LocalStorage
The storage used in environments where Local Storage is supported.
Options
The LocalStorage implementation accepts a transparent prefix. Use this when you need multiple SDK instances with
independent authentication for example.
MemoryStorage
The storage used when SDK data is ephemeral. For example: only during the lifecycle of the process.
Options
The MemoryStorage implementation accepts a transparent prefix so you can have multiple instances of the SDK without
having clashing keys.
Collections
directus.collections;
Same methods as directus.items("directus_collections").
Fields
directus.fields;
Same methods as directus.items("directus_fields").
Files
directus.files;
Same methods as directus.items("directus_files").
Folders
directus.folders;
Same methods as directus.items("directus_folders").
Permissions
directus.permissions;
Same methods as directus.items("directus_permissions").
Presets
directus.presets;
Same methods as directus.items("directus_presets").
Relations
directus.relations;
Same methods as directus.items("directus_relations").
Revisions
directus.revisions;
Same methods as directus.items("directus_revisions").
Roles
directus.roles;
Same methods as directus.items("directus_roles").
Settings
directus.settings;
Same methods as directus.items("directus_settings").
Server
Ping the Server
await directus.server.ping();
Get Server/Project Info
await directus.server.info();
Users
directus.users;
Same methods as directus.items("directus_users"), and:
Invite a New User
await directus.users.invites.send('admin@example.com', 'fe38136e-52f7-4622-8498-112b8a32a1e2');
The second parameter is the role of the user
Accept a User Invite
await directus.users.invites.accept('<accept-token>', 'n3w-p455w0rd');
The provided token is sent to the user's email
Enable Two-Factor Authentication
await directus.users.tfa.enable('my-password');
Disable Two-Factor Authentication
await directus.users.tfa.disable('691402');
Get the Current User
await directus.users.me.read();
Supports optional query:
await directus.users.me.read({
fields: ['last_access'],
});
Update the Current Users
await directus.users.me.update({ first_name: 'Admin' });
Supports optional query:
await directus.users.me.update({ first_name: 'Admin' }, { fields: ['last_access'] });
Utils
Get a Random String
await directus.utils.random.string();
Supports an optional length (defaults to 32):
await directus.utils.random.string(50);
Generate a Hash for a Given Value
await directus.utils.hash.generate('My String');
Verify if a Hash is Valid
await directus.utils.hash.verify('My String', '$argon2i$v=19$m=4096,t=3,p=1$A5uogJh');
Sort Items in a Collection
await directus.utils.sort('articles', 15, 42);
This will move item 15 to the position of item 42, and move everything in between one "slot" up.
Revert to a Previous Revision
await directus.utils.revert(451);
Note: The key passed is the primary key of the revision you'd like to apply.
TypeScript
If you are using TypeScript, the JS-SDK requires TypeScript 3.8 or newer. TypeScript will also improve the development
experience by providing relevant information when manipulating your data. For example, directus.items knows about your
collection types if you feed the SDK with enough information in the construction of the SDK instance. This allows for a
more detailed IDE suggestions for return types, sorting, and filtering.
type BlogPost = {
id: ID;
title: string;
};
type BlogSettings = {
display_promotions: boolean;
};
type MyCollections = {
posts: BlogPost;
settings: BlogSettings;
};
// This is how you feed custom type information to Directus.
const directus = new Directus<MyCollections>('http://url');
// ...
const post = await directus.items('posts').readOne(1);
// typeof(post) is a partial BlogPost object
const settings = await posts.singleton('settings').read();
// typeof(settings) is a partial BlogSettings object
You can also extend the Directus system type information by providing type information for system collections as well.
import { Directus } from '@directus/sdk';
// Custom fields added to Directus user collection.
type UserType = {
level: number;
experience: number;
};
type CustomTypes = {
/*
This type will be merged with Directus user type.
It's important that the naming matches a directus
collection name exactly. Typos won't get caught here
since SDK will assume it's a custom user collection.
*/
directus_users: UserType;
};
const directus = new Directus<CustomTypes>('https://api.example.com');
await directus.auth.login({
email: 'admin@example.com',
password: 'password',
});
const me = await directus.users.me.read();
// typeof me = partial DirectusUser & UserType;
// OK
me.level = 42;
// Error TS2322: Type "string" is not assignable to type "number".
me.experience = 'high';