Use Tableland with Next.js
Quickly set up a Next.js application with Tableland via an account and database connection.
1. Installation & setup
Create a Next.js app and install Tableland as a dependency.
- JavaScript
 - TypeScript
 
npx create-next-app --js my-tableland-app
npx create-next-app --ts my-tableland-app
Then, cd into the project and install Tableland.
- npm
 - Yarn
 - pnpm
 
npm install --save @tableland/sdk
yarn add @tableland/sdk
pnpm add @tableland/sdk
Under src/app, go to the app.js component, and import Database from @tableland/sdk. The SDK already provides access to ethers v6—for setting up an account connection, you should also import Signer and BrowserProvider. Lastly, we'll import useState from react for a simple way to track the signer in your application's state.
- JavaScript
 - TypeScript
 
"use client";
import { Database } from "@tableland/sdk";
import { BrowserProvider } from "ethers";
import { useState } from "react";
export function Home() {
  return <></>;
}
"use client";
import { Database } from "@tableland/sdk";
import { Signer, BrowserProvider } from "ethers";
import { useState } from "react";
declare const window: any;
export default function Home() {
  return <></>;
}
2. Connect to a signer
All database creates and writes need a Signer. Create a connectSigner method that prompts the browser for a wallet connection, but note there are others ways to create and track an account connection using purpose built web3 libraries (like wagmi). Here, we'll set this up without additional dependencies.
- JavaScript
 - TypeScript
 
async function connectSigner() {
  // Establish a connection with the browser wallet's provider.
  const provider = new BrowserProvider(window.ethereum);
  // Request the connected accounts, prompting a browser wallet popup to connect.
  await provider.send("eth_requestAccounts", []);
  // Create a signer from the returned provider connection.
  const signer = provider.getSigner();
  // Return the signer
  return signer;
}
export default function Home() {
  const [signer, setSigner] = useState();
  return <div></div>;
}
async function connectSigner(): Promise<Signer> {
  // Establish a connection with the browser wallet's provider.
  const provider = new r(window.ethereum);
  // Request the connected accounts, prompting a browser wallet popup to connect.
  await provider.send("eth_requestAccounts", []);
  // Create a signer from the returned provider connection.
  const signer = provider.getSigner();
  // Return the signer
  return signer;
}
export default function Home() {
  const [signer, setSigner] = useState<Signer>();
  return <div></div>;
}
You'll want create some way of calling this connect method, such as a "connect" wallet button with some click handler.
- JavaScript
 - TypeScript
 
export function Home() {
  const [signer, setSigner] = useState();
  async function handleConnect() {
    // Connect a signer
    const signer = await connectSigner();
    setSigner(signer);
  }
  return (
    <div>
      <button onClick={handleConnect}>
        {signer ? "Connected!" : "Connect"}
      </button>
    </div>
  );
}
export default function Home() {
  const [signer, setSigner] = useState<Signer>();
  async function handleConnect() {
    // Connect a signer
    const signer = await connectSigner();
    setSigner(signer);
  }
  return (
    <div>
      <button onClick={handleConnect}>
        {signer ? "Connected!" : "Connect"}
      </button>
    </div>
  );
}
3. Connect to a Database
Setting up a connection is rather straightforward once you have a signer. Simply create a database instance and pass the signer as a parameter upon instantiation. Depending on your setup, you might want specific methods that take a signer as a parameter and pass it to a Database instantiation.
- JavaScript
 - TypeScript
 
async function handleConnect() {
  // Connect a signer
  const signer = await connectSigner();
  setSigner(signer);
}
async function connectDatabase(signer) {
  // Establish a connection with the database
  const db = new Database({ signer });
  // Do create, write, and read operations
}
export default function Home() {
  const [signer, setSigner] = useState();
  async function handleConnect() {
    // Connect a signer
    const signer = await connectSigner();
    setSigner(signer);
    // Connect and interact with the database
    await connectDatabase(signer);
  }
  return (
    <div>
      <button onClick={handleConnect}>
        {signer ? "Connected!" : "Connect"}
      </button>
    </div>
  );
}
async function handleConnect() {
  // Connect a signer
  const signer = await connectSigner();
  setSigner(signer);
}
async function connectDatabase(signer: Signer) {
  // Establish a connection with the database
  const db = new Database({ signer });
  // Do create, write, and read operations
}
export default function Home() {
  const [signer, setSigner] = useState<Signer>();
  async function handleConnect() {
    // Connect a signer
    const signer = await connectSigner();
    setSigner(signer);
    // Connect and interact with the database
    await connectDatabase(signer);
  }
  return (
    <div>
      <button onClick={handleConnect}>
        {signer ? "Connected!" : "Connect"}
      </button>
    </div>
  );
}
The example here establishes a connection but doesn't do anything else—you could imagine methods that handle table creations and writing data which could take a signer, create a database connection, and do some fine tuned operations.
Also, you could track a database singleton using the useState hook, similar to how the signer is demonstrated.
- JavaScript
 - TypeScript
 
async function connectDatabase(signer) {
  // Establish a connection with the database
  const db = new Database({ signer });
  // Return the database instance
  return db;
}
export default function Home() {
  const [signer, setSigner] = useState();
  const [database, setDatabase] = useState();
  async function handleConnect() {
    // Connect a signer
    const signer = await connectSigner();
    setSigner(signer);
    // Connect and interact with the database
    const database = await connectDatabase(signer);
    setDatabase(database);
  }
  return (
    <div>
      <button onClick={handleConnect}>
        {signer ? "Connected!" : "Connect"}
      </button>
    </div>
  );
}
async function connectDatabase(signer: Signer): Promise<Database> {
  // Establish a connection with the database
  const db = new Database({ signer });
  // Return the database instance
  return db;
}
export default function Home() {
  const [signer, setSigner] = useState<Signer>();
  const [database, setDatabase] = useState<Database>();
  async function handleConnect() {
    // Connect a signer
    const signer = await connectSigner();
    setSigner(signer);
    // Connect and interact with the database
    const database = await connectDatabase(signer);
    setDatabase(database);
  }
  return (
    <div>
      <button onClick={handleConnect}>
        {signer ? "Connected!" : "Connect"}
      </button>
    </div>
  );
}
Putting it all together
Here is the final code from above, all in one place.
- JavaScript
 - TypeScript
 
"use client";
import { Database } from "@tableland/sdk";
import { BrowserProvider } from "ethers";
import { useState } from "react";
async function connectSigner() {
  // Establish a connection with the browser wallet's provider.
  const provider = new BrowserProvider(window.ethereum);
  // Request the connected accounts, prompting a browser wallet popup to connect.
  await provider.send("eth_requestAccounts", []);
  // Create a signer from the returned provider connection.
  const signer = provider.getSigner();
  // Return the signer
  return signer;
}
async function connectDatabase(signer) {
  // Establish a connection with the database
  const db = new Database({ signer });
  // Return the database instance
  return db;
}
export default function Home() {
  const [signer, setSigner] = useState();
  const [database, setDatabase] = useState();
  async function handleConnect() {
    // Connect a signer
    const signer = await connectSigner();
    setSigner(signer);
    // Connect and interact with the database
    const database = await connectDatabase(signer);
    setDatabase(database);
  }
  return (
    <div>
      <button onClick={handleConnect}>
        {signer ? "Connected!" : "Connect"}
      </button>
    </div>
  );
}
"use client";
import { Database } from "@tableland/sdk";
import { Signer, BrowserProvider } from "ethers";
import { useState } from "react";
declare const window: any;
async function connectSigner(): Promise<Signer> {
  // Establish a connection with the browser wallet's provider.
  const provider = new BrowserProvider(window.ethereum);
  // Request the connected accounts, prompting a browser wallet popup to connect.
  await provider.send("eth_requestAccounts", []);
  // Create a signer from the returned provider connection.
  const signer = provider.getSigner();
  // Return the signer
  return signer;
}
async function connectDatabase(signer: Signer): Promise<Database> {
  // Establish a connection with the database
  const db = new Database({ signer });
  // Return the database instance
  return db;
}
export default function Home() {
  const [signer, setSigner] = useState<Signer>();
  const [database, setDatabase] = useState<Database>();
  async function handleConnect() {
    // Connect a signer
    const signer = await connectSigner();
    setSigner(signer);
    // Connect and interact with the database
    const database = await connectDatabase(signer);
    setDatabase(database);
  }
  return (
    <div>
      <button onClick={handleConnect}>
        {signer ? "Connected!" : "Connect"}
      </button>
    </div>
  );
}
Dealing with local-only private keys
For private variable like wallet private keys, these should be placed in a .env.local file, which is only available server-side. Our component above only connects to things client-side, so you'd need to make some adjustments.
PRIVATE_KEY=your_wallet_private_key
Public variable should exist in a .env file in your project's root and save your private key here. For Next to read an environment variable on the client-side, you'll need to prefix it with NEXT_PUBLIC.
NEXT_PUBLIC_VAR=your_public_variable
Then, you can access these with the pattern: process.env.NEXT_PUBLIC_VAR.