Tursoを軽く触ってみた

# はじめに
Tursoを触ってみたので、Next.jsでTursoからデータ取得ができるまでのやり方を書いていきます。

# ドキュメント等
https://turso.tech/

https://docs.turso.tech/introduction

# Tursoとはなんぞや
Tursoは軽量で高速なSQLiteデータベースをベースにしたクラウドデータベースサービスです。
以下はTursoの主な特徴です：

### アーキテクチャと特徴
- SQLiteベース：SQLiteの安定性と互換性を受け継いでいます
- libSQL：TursoはlibSQL（SQLiteのフォーク）を使用して拡張機能を提供
- エッジデプロイメント：グローバルなエッジロケーションにデータを配置できます

### パフォーマンスと設計について
- 高速：低レイテンシで動作するように設計されています
- 小型：リソース消費が少なく、軽量です
- 組み込みレプリケーション機能

### UXについて
- CLIツールを提供

https://docs.turso.tech/cli/introduction

- 複数の言語のSDKをサポート

# Tursoでデータベース作成
Tursoを使用するにはアカウントを作成する必要があります。
以下から作成をしてください。

https://turso.tech/

### Turso CLIをインストール
Turso CLIをインストールしておくと便利なので、インストールしておきます。
以下のドキュメントの手順に沿ってインストールできます。（Turso CLIを使わなくてもデータベースなどの作成などはできます。）

https://docs.turso.tech/cli/introduction

Macの場合は、以下のコマンドでインストールできます。
```
brew install tursodatabase/tap/turso
```

以下のコマンドで、Tursoにログインできます。
```
turso auth signup
```

### データベース、テーブル作成手順
①以下のコマンドでテーブルを作成します。
```
turso db create test
```

②作成されているかを確認
以下のURLを開き、右上の「Login」を開くと、自分のダッシュボード画面に遷移するので、「Database」タブを開いて「test」が作成されているかを確認します。

https://turso.tech/

以下のように作成されているはず

![スクリーンショット 2025-03-31 17.01.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613586/b1d44e7a-b3e8-48c2-b8c3-8497db156779.png)

③テーブルを作成
- 「test」を開き、「Outerbase Studio」を開きます。
![スクリーンショット 2025-03-31 17.02.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613586/754bbfdb-449a-43ba-9ff6-0801535f9d1d.png)
↓
![スクリーンショット 2025-03-31 17.06.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613586/499c93ee-7fb1-45e5-9c2a-790a89839596.png)

- 「New+」をクリックし、以下のようにテーブルの情報を設定し、「Save」をクリックすると、テーブルが作成されます。
![スクリーンショット 2025-03-31 17.07.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613586/ce69d709-e8e5-40d3-bd0a-1a560ce93676.png)
↓「Save」をクリック
![スクリーンショット 2025-03-31 17.09.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613586/f2c0cbcd-e25d-4996-80d5-58ed09a5e4da.png)

④データを追加
以下のようにデータを追加します。
![スクリーンショット 2025-03-31 17.11.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613586/06991cb0-d5b6-4b65-a687-a56dd8b47c6b.png)

これでテーブルの準備は完了です。


# Next.jsアプリケーションからTursoにアクセスしてみよう
Tursoのデータベースにアクセスするには、トークンの作成とアクセスしたいデータベースのURLを取得する必要があります。

## トークンの作成、取得
まずは、CLIで以下のコマンドを実行しましょう。
```
turso db tokens create test
```
実行後、トークンが作成され、ターミナルに出力されるので、コピーしておきましょう。

 ## URLを取得
 URLは以下からコピーできます。
 ![スクリーンショット 2025-03-31 17.16.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613586/2106cd4f-0487-486c-9981-41acc960a59d.png)

## Next.jsアプリケーションから呼び出してみよう
以下のコードを作成します。
``` .env.local
TURSO_DATABASE_URL="取得したURL"
TURSO_AUTH_TOKEN="取得したトークン"
```

``` /lib/db.ts
import { createClient } from "@libsql/client";

const url = process.env.TURSO_DATABASE_URL;
const authToken = process.env.TURSO_AUTH_TOKEN;

if (!url || !authToken) {
  throw new Error('TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be set');
}

export const turso = createClient({
  url,
  authToken,
});
```

``` /api/item/route.ts
import { createClient } from "@libsql/client";

const client = createClient({
  url: process.env.TURSO_DATABASE_URL || "libsql://test.turso.io",
  authToken: process.env.TURSO_AUTH_TOKEN,
});

async function initializeDb() {
  try {
    await client.execute(`
      CREATE TABLE IF NOT EXISTS items (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL
      )
    `);
  } catch (error) {
    console.error("Error initializing database:", error);
  }
}

initializeDb();

export async function GET() {
  try {
    const result = await client.execute("SELECT * FROM items ORDER BY id DESC");
    return Response.json(result.rows);
  } catch (error) {
    console.error("Error fetching items:", error);
    return Response.json({ error: "Failed to fetch items" }, { status: 500 });
  }
}

export async function POST(request: Request) {
  try {
    const { name } = await request.json();

    if (!name || typeof name !== "string") {
      return Response.json({ error: "Name is required" }, { status: 400 });
    }

    await client.execute({
      sql: "INSERT INTO items (name) VALUES (?)",
      args: [name],
    });

    return Response.json({ success: true });
  } catch (error) {
    console.error("Error adding item:", error);
    return Response.json({ error: "Failed to add item" }, { status: 500 });
  }
}

export async function DELETE(request: Request) {
  try {
    const { searchParams } = new URL(request.url);
    const id = searchParams.get("id");

    if (!id) {
      return Response.json({ error: "ID is required" }, { status: 400 });
    }

    await client.execute({
      sql: "DELETE FROM items WHERE id = ?",
      args: [id],
    });

    return Response.json({ success: true });
  } catch (error) {
    console.error("Error deleting item:", error);
    return Response.json({ error: "Failed to delete item" }, { status: 500 });
  }
}

```

``` page.tsx
import { turso } from "@/lib/db";

export default async function Home() {
  const db = await turso.execute("SELECT * FROM users");
  const users = db.rows;

  return (
    <div className="grid grid-rows-[auto_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-8 sm:p-20 font-[family-name:var(--font-geist-sans)]">
      <h1 className="text-2xl font-bold">Turso Demo App</h1>

      <div className="w-full max-w-4xl">
        <h2 className="text-xl mb-4">ユーザー一覧</h2>

        {users.length > 0 ? (
          <div className="overflow-x-auto">
            <table className="min-w-full bg-white border border-gray-200">
              <thead className="bg-gray-100">
                <tr>
                  {Object.keys(users[0]).map((key) => (
                    <th key={key} className="px-4 py-2 text-left border-b">
                      {key}
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {users.map((user, index) => (
                  <tr
                    key={index}
                    className={index % 2 === 0 ? "bg-gray-50" : "bg-white"}
                  >
                    {Object.values(user).map((value, valueIndex) => (
                      <td key={valueIndex} className="px-4 py-2 border-b">
                        {value !== null ? String(value) : "NULL"}
                      </td>
                    ))}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        ) : (
          <p>ユーザーデータがありません</p>
        )}
      </div>

      <p className="text-sm text-gray-500">© 2025 Turso Demo</p>
    </div>
  );
}
```

以下のようにデータを取得できました！
![スクリーンショット 2025-03-31 17.31.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613586/c29be1fa-ef80-4d1f-9e02-ab6d797f05fb.png)


# 最後に

他にも色々な記事を書いているので、よければ読んでいってください！

https://qiita.com/hukuryo/items/b3b8f7b79c842b38e889

https://qiita.com/hukuryo/items/4a02784929b0cf0404a5

https://qiita.com/hukuryo/items/2a649d2ee018308d89f6

https://qiita.com/hukuryo/items/4e957621682a25da5f3b

https://qiita.com/hukuryo/items/e14779aeb0c0cf26c2d2