---
title: "@unikvs/s3.node"
description: "Learn how to use @unikvs/s3.node to store data in S3-compatible object storage from Node.js."
---

## Overview [#overview]

`@unikvs/s3.node` is a Node.js-only storage plugin that saves byte arrays to S3-compatible object storage. Node.js only It depends on AWS SDK for JavaScript v3, using `@aws-sdk/client-s3` for object operations and `Upload` from `@aws-sdk/lib-storage` for stream uploads.

Install it as follows.

```package-install
npm install @unikvs/s3.node @aws-sdk/client-s3 @aws-sdk/lib-storage
```

## Usage [#usage]

The storage class is `S3`.

```ts
import { S3 } from "@unikvs/s3.node";
```

```ts
new S3(bucket: string, config: S3ClientConfig = {}, options: S3StorageOptions = {})
```

| Argument | Type | Description |
| ---- | -- | ---- |
| `bucket` | `string` | The destination bucket name. All operations use it. |
| `config` | `S3ClientConfig` | Settings passed to `S3Client`. Defaults to an empty object. Includes region and credentials. |
| `options` | `S3StorageOptions` | Behavioral settings for the storage. Defaults to an empty object. |
| `options.allowRepair` | `boolean` | Whether to allow repair writes. Defaults to `false`. When `false`, `write` and `getWritable` for repairs (where `vars["unikvs:repair"]` is `true`) throw `RepairNotAllowedError`. |

There is no argument for a pre-built `S3Client` or a prefix. For key mapping or bucket separation, include it in the key or create instances per bucket.

:::note
Credential resolution is delegated to the standard AWS SDK mechanisms. This package has no proprietary auth options. Pass `credentials`, `region`, and `endpoint` via `config`, or use env vars or shared config files.
:::

The lifecycle is as follows. Right after instantiation, `isOpen` is `false`. Calling `open()` creates an internal `S3Client` and sets `true`. Calling `close()` destroys it and resets to `false`. `open()` and `close()` take no arguments.

```ts
import { UniKvs } from "unikvs";
import { S3 } from "@unikvs/s3.node";

const storage = new S3("my-bucket", {
  region: "ap-northeast-1",
});

const kvs = UniKvs.config()
  .appendStorage(storage)
  .create();
```

:::warning
Creating the bucket is not this package's responsibility. Create it beforehand via the console or similar tooling.
:::

## Object Layout [#layout]

Keys map directly to object keys. No prefixing or escaping is applied.

| UniKVS key | S3 object key |
| ------------- | --------------------- |
| `test.txt` | `test.txt` |
| `folder/テスト #123.dat` | `folder/テスト #123.dat` |

Single writes use `PutObjectCommand`, reads use `GetObjectCommand`, existence checks use `HeadObjectCommand`, and deletions use `DeleteObjectCommand`.

`clear` deletes all objects in the bucket. No prefix filtering is applied. It paginates with `ListObjectsV2Command` and deletes with `DeleteObjectsCommand`. Use a dedicated bucket for UniKVS. Shared buckets lose other apps' data too.

## Streams and Multipart [#multipart]

A single `write` saves with `PutObjectCommand`, and a single `read` converts the `GetObjectCommand` result with `transformToByteArray`.

Writable streams use `Upload` from `@aws-sdk/lib-storage`, so large payloads benefit from multipart upload. Obtain one as follows.

```ts
const writable = storage.getWritable({ key, vars, signal });
```

The arguments are `vars`, `key`, and `signal`. The part size is read from `vars`. The `@unikvs/s3.node:partSize` key takes precedence, falling back to `@unikvs/s3:partSize`. Specify a positive integer in bytes. When omitted, the `Upload` defaults apply.

:::tip
For large data, use a writable stream instead of a single `write`. It supports multipart upload.
:::

If `signal` is already aborted, it throws `StorageAbortedError` without starting. If `signal` aborts mid-write, the reason is forwarded to the internal `AbortController`. `close()` waits for completion, while `abort()` attempts cancellation.

Obtain a readable stream as follows.

```ts
const readable = await storage.getReadable({ key, signal });
```

Internally it sends a `GetObjectCommand` and converts the result with `transformToWebStream`. Large objects can be read in multiple chunks.

## Errors [#errors]

| Error | When thrown |
| --- | --- |
| `InvalidPartSizeError` | Thrown by `getWritable` when the part size in `vars` is not a positive integer. A shared error from `@unikvs/core`. |
| `StorageAbortedError` | Thrown when the `signal` for `getWritable` is already aborted. A shared error from `@unikvs/core`. |

- `read` on a missing key fails with an error. Use `exists` beforehand when needed.
- `delete` on a missing key does not fail.

## Examples [#examples]

A minimal example against S3-compatible storage. The endpoint and credentials are dummy values for common S3-compatible storage (such as MinIO). Create the bucket beforehand.

```ts
import { S3 } from "@unikvs/s3.node";

const storage = new S3("my-bucket", {
  endpoint: "http://127.0.0.1:9000",
  region: "ap-northeast-1",
  credentials: {
    accessKeyId: "minioadmin",
    secretAccessKey: "minioadmin",
  },
  forcePathStyle: true,
});

storage.open();

const signal = AbortSignal.timeout(30_000);
const key = "hello.txt";
const data = new TextEncoder().encode("Hello S3");

await storage.write({ key, data, signal });
const loaded = await storage.read({ key, signal });
console.log(new TextDecoder().decode(loaded));

storage.close();
```
