Skip to main content
Blog / Build with Tigris

Announcing the Tigris Storage SDK

Xe Iaso
Senior Cloud Whisperer
Abdullah Ibrahim
Senior Software Engineer
A blue tiger with a laptop under his shoulder walks to work, greeting a floating robotic datacentre technician on his way to his office.

A blue tiger with a laptop under his shoulder walks to work, greeting a floating robotic datacentre technician on his way to his office.

At Tigris, we do object storage. However, in order to use Tigris in your JavaScript or TypeScript projects, you normally have to import a library from AWS. We want you to import Tigris to use Tigris, so we made the Tigris Storage SDK to make interactions with object storage simpler. Here’s how you use it compared to the AWS S3 SDK:

Tigris Storage SDK

import { put } from "@tigrisdata/storage";

await put("object.txt", "Hello, World!");

AWS S3 SDK

import {
S3Client,
PutObjectCommand
} from "@aws-sdk/client-s3";

const s3Client = new S3Client({
region: "us-east-1",
});

const data = await s3Client.send(
new PutObjectCommand({
Bucket: "your-bucket-name",
Key: "object.txt",
Body: "hello world",
ContentType: "text/plain",
})
);

You can get started with the Tigris Storage SDK by installing the NPM package @tigrisdata/storage:

npm install --save @tigrisdata/storage

From there, create a bucket, create an Editor keypair for that bucket, and add the following to your .env file (or to the project’s environment in your runtime of choice):

TIGRIS_STORAGE_ACCESS_KEY_ID=tid_access_key_id
TIGRIS_STORAGE_SECRET_ACCESS_KEY=tsec_secret_access_key
TIGRIS_STORAGE_BUCKET=mybucket

That’s it!

📦 npm💻 GitHub📓 Examples🤓 Docs

Why make a storage SDK?

Object storage is a very simple concept that ended up being complicated in practice. When we designed the Tigris Storage SDK, we wanted to pare it down to the bare essentials. If you need to put an object into Tigris, you use the put function. This automatically figures out where to store things, what credentials to use, and all the other minutæ you have to deal with. You just put, get, list, and delete objects.

This adds up to give you the ability to glance at the code and have a good idea of what is actually going on. Compare these two code snippets:

Tigris Storage SDK

import { list } from "@tigrisdata/storage";

const { data, error } =
await list({ limit: 100 });

if (error !== undefined) {
console.error("Error listing files:", error);
}

data.forEach(({ name, size }) => {
console.log(`${name}: ${size} bytes`);
});

AWS S3 SDK

import {
S3Client,
ListObjectsV2Command
} from "@aws-sdk/client-s3";

const s3Client = new S3Client({
region: "us-east-1"
});

const command = new ListObjectsV2Command({
Bucket: "my-bucket",
MaxKeys: 100,
});
const response = await s3Client.send(command);

response.Contents.forEach((item) => {
console.log(`${item.Key}: ${item.Size} bytes`);
});

Which one would you rather read in your codebase? The cognitive complexity reduction in the Tigris Storage SDK is subtle but significant. By default, the Tigris Storage SDK loads all of its configuration from the environment like any good twelve-factor app should:

Tigris Storage SDKAWS
TIGRIS_STORAGE_ACCESS_KEY_IDAWS_ACCESS_KEY_ID
TIGRIS_STORAGE_SECRET_ACCESS_KEYAWS_SECRET_ACCESS_KEY
TIGRIS_STORAGE_BUCKETNo equivalent, you must set it manually per call
  • AWS_ACCESS_KEY_ID ➡️ TIGRIS_STORAGE_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY ➡️ TIGRIS_STORAGE_SECRET_ACCESS_KEY
  • TIGRIS_STORAGE_BUCKET ➡️ No equivalent, you must set it manually per call

However, if you need to put things into a different bucket, you can do that with the config argument:

import { put } from "@tigrisdata/storage";

const { data, error } = put(`avatars/${user.id}`, avatarBlob, {
addRandomSuffix: true,
config: {
bucket: "contoso-user-data",
},
});

if (error !== undefined) {
throw error; // or whatever else you do in your project
}

console.log(`Data uploaded to tigris as ${data.path}`);
console.log(`URL: ${data.url}`);

This will put a user’s avatar data into the Tigris bucket contoso-user-data and suffix the name with the time (in Unix milliseconds) and random data. You can also set the access key id, secret access key, and API endpoint on a per-bucket basis.

You may have noticed something interesting in that last example:

const { data, error } = put(`avatars/${user.id}`, avatarBlob);

if (error !== undefined) {
throw error; // or whatever else you do in your project
}

// use data

When possible, we return errors as values instead of throwing exceptions up the stack. This pattern allows you to handle errors inline to your code so they aren’t surprises when they come up in practice. This adds up to reduce the cognitive complexity of your code so you can go back to shipping your multicloud agentic SaaS of your dreams!

This also means you don’t have to instantiate classes or ferry around a client global when you want to do basic operations:

Tigris Storage SDK

import { get } from "@tigrisdata/storage";

const { data, error } = get("object.txt", "string");

if (error !== undefined) {
// or whatever else you do in your project
throw error;
}

console.log(data);

AWS S3 SDK

import {
S3Client,
GetObjectCommand
} from "@aws-sdk/client-s3";

const s3Client = new S3Client({
region: "us-east-1"
});

const command = new GetObjectCommand({
Bucket: "my-bucket",
Key: "object.txt",
});
const response = await s3Client.send(command);
const data = response.Body.transformToString("utf-8");

console.log(data);

Need to upload a large file? The multipart upload feature is there for you!

const videoStream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("Hello, World!"));
controller.close();
},
});
const { data, error } = await put("videos/large-video.mp4", videoStream, {
multipart: true,
onUploadProgress: ({ percentage }) => {
console.log(`Upload progress: ${percentage}%`);
},
});

if (error !== undefined) {
throw error; // or whatever else you do in your project
}

console.log(data);

Client uploads

Tigris does not charge egress fees, but the provider you run your app on likely does. This means that uploading data to Tigris from your app can run up a cost per gigabyte. To work around this, we’ve created the client upload pattern. This makes your app return a presigned PUT URL to the client and then the client uploads the data directly to Tigris, sidestepping the entire part where your app needs to stream data from the client and then upload it to Tigris.

In order to implement this, you need to do the following:

  • Expose an upload endpoint that creates pre-signed URLs, for example: /api/upload
  • Call the upload function from @tigrisdata/storage/client module like this:
import { upload } from "@tigrisdata/storage/client";

const { data, error } = await upload(file.name), file, {
url: "/api/upload", // or whatever endpoint you use
access: "private", // or public
onUploadProgress: ({ loaded, total, percentage }) => {
console.log({ loaded, total, percentage });
},
};

if (error !== undefined) {
throw error; // or whatever you do normally
}

console.log(data);

And then your data is uploaded by the client instead of having to pipe it through to the server! Make sure to secure this API endpoint appropriately, add rate limits, and all the other protections you should do for production services. Note that you may need to combine this with setting CORS for your bucket and using custom branded presigned URLs for the best effect.

Conclusion

If you want to try the Tigris Storage SDK, install it from npm:

npm install --save @tigrisdata/storage

If you want to learn more about the Tigris Storage SDK, check out these links for more information:

📦 npm💻 GitHub📓 Examples🤓 Docs

Globally performant object storage

Tigris is dedicated to making sure that your experience with object storage is the best it can possibly be, and then we go the extra mile to make it even better.