|
3 | 3 | import Tabs from "@theme/Tabs"; |
4 | 4 | import TabItem from "@theme/TabItem"; |
5 | 5 |
|
6 | | -The Tigris Storage SDK is a simplified wrapper around the Tigris Data API. It |
7 | | -provides a simpler interface and minimal configuration that lets you get started |
8 | | -quickly and integrate Tigris into your application. It offers all the |
9 | | -functionality of the Tigris Data, but with a simpler interface. |
| 6 | +The Tigris Storage SDK is a simplified wrapper around the Tigris Object Storage |
| 7 | +API. It provides a simpler interface and minimal configuration that lets you get |
| 8 | +started quickly and integrate Tigris into your application. It offers all the |
| 9 | +functionality of the Tigris, but with a simpler interface. |
10 | 10 |
|
11 | | -Tigris Storage SDK is geared towards application developers who needs to store |
12 | | -objects that: |
| 11 | +## Use Cases |
| 12 | + |
| 13 | +Tigris Storage SDK is geared (but not limited to) towards application developers |
| 14 | +who needs to store objects that: |
13 | 15 |
|
14 | 16 | - Are programmatically uploaded or generated at build time, for display and |
15 | 17 | download such as avatars, screenshots, cover images and videos |
16 | 18 | - Are larger and not practical to store in the database, such as images, videos, |
17 | | - etc. |
| 19 | + documents, etc. |
18 | 20 | - That needs to be retrieved frequently across different regions |
| 21 | +- break your wallet by paying egress fees |
| 22 | + |
| 23 | +## Key Features |
| 24 | + |
| 25 | +As mentioned above, Tigris Storage SDK offers all the great features of the |
| 26 | +Tigris Object Storage API. Some of the key features are: |
| 27 | + |
| 28 | +- **[Global](/docs/objects/object_regions/):** Access your data from any region |
| 29 | + by using a single global endpoint. Tigris is inherently global. However, if |
| 30 | + you need to store data in single or multiple regions, you can do so by simply |
| 31 | + configuring the bucket regions. Check out the |
| 32 | + [Multi-Region Buckets](/docs/buckets/multi-region/) section. |
| 33 | +- **[Zero egress fees](/docs/pricing/):** Access your data freely. Stop paying |
| 34 | + just to use your own data. From storing large datasets for your AI workflows |
| 35 | + to storing small files, those egress fees can add up quickly. Tigris offers |
| 36 | + world class object storage for the fraction of the cost of other providers. |
| 37 | +- **[Blazing fast](/blog/benchmark-small-objects/):** Tigris is 5.3x faster than |
| 38 | + S3 and 86.6x faster than R2 for small object reads. If your workload depends |
| 39 | + on reading lots of small objects—logs, AI features, or massive number of tiny |
| 40 | + files—this matters. |
19 | 41 |
|
20 | 42 | ## Getting Started |
21 | 43 |
|
@@ -71,7 +93,7 @@ supports both CommonJS and ES6 Modules syntax. That means you can use both |
71 | 93 | ```js |
72 | 94 | import { list } from "@tigrisdata/storage"; |
73 | 95 |
|
74 | | -const objects = await list(); // lists objects in bucket $BUCKET_NAME |
| 96 | +const objects = await list(); // lists objects in bucket TIGRIS_STORAGE_BUCKET |
75 | 97 | console.log(objects); |
76 | 98 | ``` |
77 | 99 |
|
@@ -99,143 +121,211 @@ more than happy to add in examples. |
99 | 121 |
|
100 | 122 | ### Viewing and downloading files |
101 | 123 |
|
102 | | -`get` function can be used to get a file from a bucket. |
| 124 | +`get` function can be used to get a file from a bucket. `contentDisposition` |
| 125 | +option can be to either `attachment` or `inline` depending on whether you want |
| 126 | +to trigger a download or display the file in the browser. |
103 | 127 |
|
104 | 128 | <Tabs> |
105 | | -<TabItem value="es6mod" label="ES6 Modules" default> |
| 129 | +<TabItem value="server" label="Server" default> |
106 | 130 |
|
107 | | -```js |
| 131 | +```ts |
| 132 | +// app/api/avatar.ts |
| 133 | +import { NextRequest, NextResponse } from "next/server"; |
108 | 134 | import { get } from "@tigrisdata/storage"; |
109 | 135 |
|
110 | | -const result = await get("my-file.jpg"); |
111 | | -``` |
112 | | - |
113 | | -</TabItem> |
114 | | -<TabItem value="commonjs" label="CommonJS Modules"> |
| 136 | +export async function GET( |
| 137 | + req: NextRequest, |
| 138 | +): Promise<NextResponse<File | string>> { |
| 139 | + const avatar = req.nextUrl.searchParams.get("avatar"); |
115 | 140 |
|
116 | | -```js |
117 | | -const { get } = require("@tigrisdata/storage"); |
| 141 | + if (!avatar) { |
| 142 | + return NextResponse.json("avatar parameter is required", { status: 400 }); |
| 143 | + } |
118 | 144 |
|
119 | | -get("my-file.jpg").then((result) => { |
120 | | - console.log(result); |
121 | | -}); |
122 | | -``` |
| 145 | + try { |
| 146 | + const avatarPath = decodeURIComponent(avatar as string); |
123 | 147 |
|
124 | | -</TabItem> |
125 | | -</Tabs> |
| 148 | + const file = await get(avatarPath, "file", { |
| 149 | + contentDisposition: "inline", |
| 150 | + }); |
126 | 151 |
|
127 | | -To trigger a download, you can use the `contentDisposition` option. |
| 152 | + if (file.data) { |
| 153 | + return new NextResponse(file.data, { status: 200 }); |
| 154 | + } |
128 | 155 |
|
129 | | -<Tabs> |
130 | | -<TabItem value="es6mod" label="ES6 Modules" default> |
| 156 | + if (file.error && file.error.message) { |
| 157 | + return NextResponse.json(file.error.message, { status: 500 }); |
| 158 | + } |
| 159 | + } catch (error) { |
| 160 | + return NextResponse.json( |
| 161 | + error instanceof Error ? error.message : "Unknown error", |
| 162 | + { status: 500 }, |
| 163 | + ); |
| 164 | + } |
131 | 165 |
|
132 | | -```js |
133 | | -const result = await get("my-file.pdf", { contentDisposition: "attachment" }); |
| 166 | + return NextResponse.json("No data found", { status: 404 }); |
| 167 | +} |
134 | 168 | ``` |
135 | 169 |
|
136 | 170 | </TabItem> |
137 | | -<TabItem value="commonjs" label="CommonJS Modules"> |
138 | | - |
139 | | -```js |
140 | | -const { get } = require("@tigrisdata/storage"); |
141 | | - |
142 | | -get("my-file.pdf", { contentDisposition: "attachment" }).then((result) => { |
143 | | - console.log(result); |
144 | | -}); |
| 171 | +<TabItem value="client" label="Client"> |
| 172 | + |
| 173 | +```tsx |
| 174 | +import Image from "next/image"; |
| 175 | + |
| 176 | +export default function Avatar() { |
| 177 | + const { user } = getUserData(); |
| 178 | + |
| 179 | + return ( |
| 180 | + <Image |
| 181 | + src={`/api/avatar?avatar=${encodeURIComponent(user.avatar)}`} |
| 182 | + alt="Avatar" |
| 183 | + width={100} |
| 184 | + height={100} |
| 185 | + /> |
| 186 | + ); |
| 187 | +} |
145 | 188 | ``` |
146 | 189 |
|
147 | 190 | </TabItem> |
148 | 191 | </Tabs> |
149 | 192 |
|
150 | | -You can also use the `contentType` option to set the content type of the file. |
| 193 | +To trigger a download, set the `contentDisposition` option to `attachment`. |
151 | 194 |
|
152 | | -### Getting list of objects |
| 195 | +### Uploading files |
153 | 196 |
|
154 | | -`list` function can be used to get a list of objects in a bucket. |
| 197 | +`put` function can be used to upload a file to a bucket. |
155 | 198 |
|
156 | 199 | <Tabs> |
157 | | -<TabItem value="es6mod" label="ES6 Modules" default> |
| 200 | +<TabItem value="server" label="Server" default> |
158 | 201 |
|
159 | | -```javascript |
160 | | -import { list } from "@tigrisdata/storage"; |
| 202 | +```ts |
| 203 | +// app/api/upload.ts |
| 204 | +import { NextRequest, NextResponse } from "next/server"; |
| 205 | +import { put } from "@tigrisdata/storage"; |
| 206 | + |
| 207 | +export async function PUT(req: NextRequest) { |
| 208 | + const formData = await req.formData(); |
| 209 | + const file = formData.get("file") as File; |
| 210 | + |
| 211 | + if (!file) { |
| 212 | + return NextResponse.json({ error: "No file provided" }, { status: 400 }); |
| 213 | + } |
| 214 | + |
| 215 | + const result = await put(file.name, file, { |
| 216 | + access: "public", |
| 217 | + addRandomSuffix: false, |
| 218 | + }); |
| 219 | + |
| 220 | + if (result.error) { |
| 221 | + return NextResponse.json({ error: result.error.message }, { status: 500 }); |
| 222 | + } |
161 | 223 |
|
162 | | -// List first 100 objects |
163 | | -const result = await list({ limit: 100 }); |
| 224 | + return NextResponse.json({ data: result.data }, { status: 200 }); |
| 225 | +} |
164 | 226 | ``` |
165 | 227 |
|
166 | 228 | </TabItem> |
167 | | -<TabItem value="commonjs" label="CommonJS Modules"> |
168 | | - |
169 | | -```javascript |
170 | | -const { list } = require("@tigrisdata/storage"); |
| 229 | +<TabItem value="client" label="Client"> |
| 230 | + |
| 231 | +```tsx |
| 232 | +export default function Upload() { |
| 233 | + const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
| 234 | + const file = e.target.files?.[0]; |
| 235 | + |
| 236 | + if (file) { |
| 237 | + const formData = new FormData(); |
| 238 | + formData.append("file", file); |
| 239 | + fetch("/api/test", { |
| 240 | + method: "PUT", |
| 241 | + body: formData, |
| 242 | + }); |
| 243 | + } |
| 244 | + }; |
171 | 245 |
|
172 | | -// List first 100 objects |
173 | | -list({ limit: 100 }).then((result) => { |
174 | | - console.log(result); |
175 | | -}); |
| 246 | + return <input type="file" onChange={handleFileChange} />; |
| 247 | +} |
176 | 248 | ``` |
177 | 249 |
|
178 | 250 | </TabItem> |
179 | 251 | </Tabs> |
180 | 252 |
|
181 | | -Tigris Storage SDK supports pagination of results. |
| 253 | +### Client side uploads |
182 | 254 |
|
183 | | -<Tabs> |
184 | | -<TabItem value="es6mod" label="ES6 Modules" default> |
| 255 | +Amongst all the other great features of Tigris Storage SDK, free egress fees is |
| 256 | +a what makes us stand out from other providers. We care about the bandwidth |
| 257 | +costs and we want to make it as cheap as possible for you to use Tigris. That's |
| 258 | +why we've made it so that you can upload files directly to Tigris from the |
| 259 | +client side. |
| 260 | + |
| 261 | +We leverage the presigned URLs features to allow you to upload files directly to |
| 262 | +Tigris from the client side. |
185 | 263 |
|
186 | | -```javascript |
187 | | -let allFiles = []; |
188 | | -let currentPage = await list({ limit: 50 }); |
| 264 | +<Tabs> |
| 265 | +<TabItem value="server" label="Server" default> |
189 | 266 |
|
190 | | -if (currentPage.data) { |
191 | | - allFiles.push(...currentPage.data.items); |
| 267 | +```ts |
| 268 | +// app/api/signed.ts |
| 269 | +import { NextRequest, NextResponse } from "next/server"; |
| 270 | +import { getPresignedUrl } from "@tigrisdata/storage"; |
192 | 271 |
|
193 | | - while (currentPage.data.hasMore && currentPage.data.paginationToken) { |
194 | | - currentPage = await list({ |
195 | | - limit: 50, |
196 | | - paginationMarker: currentPage.data.paginationToken, |
| 272 | +export async function POST(request: NextRequest) { |
| 273 | + try { |
| 274 | + const { path, method, contentType } = await request.json(); |
| 275 | + const result = await getPresignedUrl(path, { |
| 276 | + method, |
| 277 | + contentType, |
| 278 | + expiresIn: 3600, // 1 hour |
197 | 279 | }); |
198 | 280 |
|
199 | | - if (currentPage.data) { |
200 | | - allFiles.push(...currentPage.data.items); |
201 | | - } else if (currentPage.error) { |
202 | | - console.error("Error during pagination:", currentPage.error); |
203 | | - break; |
204 | | - } |
| 281 | + return NextResponse.json({ data: result.data }); |
| 282 | + } catch (error) { |
| 283 | + console.error("Upload error:", error); |
| 284 | + return NextResponse.json( |
| 285 | + { error: "Failed to generate presigned URL" }, |
| 286 | + { status: 500 }, |
| 287 | + ); |
205 | 288 | } |
206 | 289 | } |
207 | 290 | ``` |
208 | 291 |
|
209 | 292 | </TabItem> |
210 | | -<TabItem value="commonjs" label="CommonJS Modules"> |
211 | | - |
212 | | -```javascript |
213 | | -const { list } = require("@tigrisdata/storage"); |
214 | 293 |
|
215 | | -(async () => { |
216 | | - let allFiles = []; |
217 | | - let currentPage = await list({ limit: 50 }); |
| 294 | +<TabItem value="client" label="Client"> |
| 295 | +```tsx |
| 296 | +"use client"; |
| 297 | + |
| 298 | +import { upload } from "@tigrisdata/storage/client"; |
| 299 | +import { useState } from "react"; |
| 300 | + |
| 301 | +export default function ClientUpload() { |
| 302 | + const [progress, setProgress] = useState<number>(0); |
| 303 | + const [url, setUrl] = useState<string | null>(null); |
| 304 | + const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => { |
| 305 | + const file = e.target.files?.[0]; |
| 306 | + setProgress(0); |
| 307 | + if (file) { |
| 308 | + const result = await upload(`${file.name}`, file, { |
| 309 | + url: "/api/test", |
| 310 | + access: "private", |
| 311 | + onUploadProgress: ({ loaded, total, percentage }) => { |
| 312 | + setProgress(percentage); |
| 313 | + if (percentage === 100) { |
| 314 | + setProgress(0); |
| 315 | + } |
| 316 | + }, |
| 317 | + }); |
| 318 | + setUrl(result.url); |
| 319 | + } |
| 320 | + }; |
218 | 321 |
|
219 | | - if (currentPage.data) { |
220 | | - allFiles.push(...currentPage.data.items); |
| 322 | +return ( <><input type="file" onChange={handleFileChange} /> {url && |
221 | 323 |
|
222 | | - while (currentPage.data.hasMore && currentPage.data.paginationToken) { |
223 | | - currentPage = await list({ |
224 | | - limit: 50, |
225 | | - paginationMarker: currentPage.data.paginationToken, |
226 | | - }); |
| 324 | +<div>Uploaded to: {url}</div>} {progress > 0 && progress < 100 && |
| 325 | +<div>{progress}%</div>} </> ); } |
227 | 326 |
|
228 | | - if (currentPage.data) { |
229 | | - allFiles.push(...currentPage.data.items); |
230 | | - } else if (currentPage.error) { |
231 | | - console.error("Error during pagination:", currentPage.error); |
232 | | - break; |
233 | | - } |
234 | | - } |
235 | | - } |
236 | | - console.log(allFiles); |
237 | | -})(); |
238 | 327 | ``` |
239 | 328 |
|
240 | 329 | </TabItem> |
241 | 330 | </Tabs> |
| 331 | +``` |
0 commit comments