# Quick start

> Four ways in, depending on what you are building. Pick yours; each one gets you to a working code in a few minutes.

Canonical: https://doc.s3nd.sh/docs/quick-start · Markdown: https://doc.s3nd.sh/docs/quick-start.md

s3nd is four packages, and the right one depends on **where the transfer starts**. Pick the line
that sounds like what you are building:

<Cards>
  <Card title="Move files from a terminal" href="#from-a-terminal">
    Between your own machines, or out of CI. One global install, nothing to deploy. `@s3nd/cli`
  </Card>
  <Card title="Send and receive in a React app" href="#in-a-react-app">
    A button that hands out a code, a field that takes one. `@s3nd/react` in the browser, `@s3nd/core` on your server.
  </Card>
  <Card title="Write snapshots from server code" href="#from-your-server">
    Your own routes call the bucket directly: Next.js, Hono, Express, a job. `@s3nd/core`
  </Card>
  <Card title="Talk to a s3nd server from elsewhere" href="#from-another-runtime">
    React Native, a worker, a script, another framework. `@s3nd/protocol`: `fetch` and nothing else.
  </Card>
</Cards>

| You are building            | Install                      | Runs on                      | Needs a server |
| --------------------------- | ---------------------------- | ---------------------------- | -------------- |
| A terminal workflow, CI     | `npm install -g @s3nd/cli`   | your machines                | no             |
| A React app                 | `@s3nd/react` + `@s3nd/core` | browser + your server        | yes, yours     |
| Server-side code            | `@s3nd/core`                 | Node 20+, Bun, Deno, workers | it is one      |
| A client in another runtime | `@s3nd/protocol`             | anything with `fetch`        | yes, any s3nd  |

The first three need a bucket you can write to: AWS S3, Cloudflare R2, MinIO, Scaleway, Wasabi or
any other S3-compatible storage. [Providers](https://doc.s3nd.sh/docs/providers) says what each one wants. The last
one only needs the URL of a server that already has one.

## From a terminal

<Steps>
<Step>

### Install the CLI

```sh
npm install -g @s3nd/cli
```

</Step>
<Step>

### Answer a few questions

```sh
s3nd setup
```

It asks where transfers are stored, the bucket and the keys, with where to find each one. The
answers go to `~/.config/s3nd/config.json`, readable by you only and used from any directory,
and it ends by writing, reading and deleting a probe object to prove the bucket works.

Skip this step if you like: the first command that needs a bucket offers to run it.

</Step>
<Step>

### Send, then receive

```sh
s3nd put ./report.pdf     # prints a code: K7QP2M4X
```

```sh
s3nd get k7qp-2m4x        # on the other machine; case and dashes do not matter
```

</Step>
</Steps>

Next: [the CLI reference](https://doc.s3nd.sh/docs/cli), or [Without a server](https://doc.s3nd.sh/docs/no-server) for the whole R2
setup, CI included.

## In a React app

A browser cannot hold your bucket's keys, so this path has two halves: four routes on your server,
and hooks in the browser that talk to them. The AWS SDK never reaches the client bundle.

<Steps>
<Step>

### Install both halves

```sh
npm install @s3nd/core @s3nd/react
```

</Step>
<Step>

### Mount the routes on your server

```ts title="app/api/transfers/[[...route]]/route.ts"
import { createBucket, createTransferHandler } from '@s3nd/core'

export const { GET, POST, DELETE } = createTransferHandler({
  bucket: createBucket({ bucket: process.env.S3_BUCKET, region: process.env.AWS_REGION }),
  app: 'notes',
  expiresIn: 3600,
})
```

The handler takes a `Request` and returns a `Response`, so the same line works in Hono, Bun or a
worker. Every route is public until you pass `authorize`: [Setting up a server](https://doc.s3nd.sh/docs/server).

</Step>
<Step>

### Point the provider at them

```tsx title="app/providers.tsx"
'use client'

import { S3ndProvider } from '@s3nd/react'

export function Providers({ children }: { children: React.ReactNode }) {
  return <S3ndProvider baseUrl="/api/transfers">{children}</S3ndProvider>
}
```

</Step>
<Step>

### Send on one device, receive on the other

<Tabs items={['Sending', 'Receiving']}>
<Tab value="Sending">

```tsx
import { useSendTransfer } from '@s3nd/react'

export function MoveToAnotherDevice() {
  const { send, transfer, isPending } = useSendTransfer()

  return (
    <>
      <button onClick={() => send(exportState())} disabled={isPending}>
        Move to another device
      </button>
      {transfer && <p>Type this on the other device: {transfer.code}</p>}
    </>
  )
}
```

</Tab>
<Tab value="Receiving">

```tsx
import { useReceiveTransfer, useSyncCodeInput } from '@s3nd/react'

export function RestoreFromCode() {
  const input = useSyncCodeInput()
  const { load, data, notFound } = useReceiveTransfer()

  return (
    <>
      <input {...input.inputProps} placeholder="K7QP2M4X" />
      <button onClick={() => input.code && load(input.code)} disabled={!input.isComplete}>
        Look it up
      </button>
      {notFound && <p>Unknown or expired code.</p>}
      {data && <button onClick={() => importState(data)}>Replace my data</button>}
    </>
  )
}
```

</Tab>
</Tabs>

`exportState` and `importState` are yours: only your app knows its own storage. The
[IndexedDB example](https://github.com/AbderrahmaneMouzoune/s3nd/tree/main/examples/indexeddb-sync)
has a complete pair.

</Step>
</Steps>

Next: [React](https://doc.s3nd.sh/docs/react) for every hook and option, [The transfer protocol](https://doc.s3nd.sh/docs/protocol) for
what the routes do.

## From your server

When your own routes decide what gets stored, call the bucket directly. No protocol, no client
package: a code, a snapshot, and your own two endpoints.

<Steps>
<Step>

### Install the core

```sh
npm install @s3nd/core
```

Credentials come from the options, the usual `AWS_*` variables, or the platform's role:
[Installation](https://doc.s3nd.sh/docs/installation).

</Step>
<Step>

### Create the bucket once

```ts title="lib/store.ts"
import { createBucket } from '@s3nd/core'

export const store = createBucket({
  bucket: process.env.S3_BUCKET,
  region: process.env.AWS_REGION,
  prefix: 'snapshots',
  maxSize: 4 * 1024 * 1024,
})

export const SCHEMA_VERSION = 3
```

`createBucket()` is cheap, since the `S3Client` is built on the first request, so module scope is
fine. `maxSize` below your platform's request limit turns an oversized snapshot into a clean 413.

</Step>
<Step>

### Store a snapshot under a fresh code

```ts title="app/api/sync/route.ts"
import { store, SCHEMA_VERSION } from '@/lib/store'

export async function POST(request: Request) {
  const code = store.codes.create()

  await store.putSnapshot(code, await request.json(), {
    app: 'notes',
    version: SCHEMA_VERSION,
    expiresIn: 60 * 60,
    ifAbsent: true,
  })

  return Response.json({ code })
}
```

`expiresIn` keeps a code from outliving the transfer: it is a bearer token, and a short life is
most of its security. `ifAbsent` makes a collision fail loudly instead of overwriting a stranger's
snapshot.

</Step>
<Step>

### Hand it back on the other device

```ts title="app/api/sync/[code]/route.ts"
import { store, SCHEMA_VERSION } from '@/lib/store'

export async function GET(_request: Request, { params }: { params: Promise<{ code: string }> }) {
  const { code } = await params
  const snapshot = await store.getSnapshot(store.codes.normalize(code), { maxVersion: SCHEMA_VERSION })

  if (!snapshot) return Response.json({ error: 'Unknown or expired code' }, { status: 404 })

  return Response.json({ data: snapshot.data, createdAt: snapshot.createdAt })
}
```

`store.codes.normalize()` is what makes a typed code work: separators dropped, case folded, the
characters people misread repaired. Call it on user input, always. `maxVersion` makes a snapshot
from a newer build throw rather than land in an app that would misread it.

</Step>
</Steps>

Next: [Snapshots](https://doc.s3nd.sh/docs/snapshots) for what is in the envelope, [Sync codes](https://doc.s3nd.sh/docs/sync-codes) for
how guessable a code really is, [Two devices, one snapshot](https://doc.s3nd.sh/docs/two-devices) for when both sides
write.

## From another runtime

Anything with `fetch` can talk to a s3nd server, whether `createTransferHandler()` runs it or
something written in Go does:

```sh
npm install @s3nd/protocol
```

```ts
import { createTransferClient } from '@s3nd/protocol'

const transfers = createTransferClient({ baseUrl: 'https://drop.example.com/api/transfers' })

const { code } = await transfers.createSnapshot({ data: state, version: 3 })
const incoming = await transfers.read(typedCode) // null when unknown or expired
```

Its whole dependency tree is nanoid; no path from it reaches the AWS SDK, so it bundles for React
Native or a worker as is. Next: [The transfer protocol](https://doc.s3nd.sh/docs/protocol).
