Skip to main content

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.

npx create-next-app --js my-tableland-app

Then, cd into the project and install Tableland.

npm install --save @tableland/sdk

Under src/app, go to the app.js component, and import Database from @tableland/sdk. The SDK already provides access to ethers v5—for setting up an account connection, you should also import Signer and providers. Lastly, we'll import useState from react for a simple way to track the signer in your application's state.

src/app/page.js
"use client";

import { Database } from "@tableland/sdk";
import { providers } from "ethers";
import { useState } from "react";

export 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.

src/app/page.js
async function connectSigner() {
// Establish a connection with the browser wallet's provider.
const provider = new providers.Web3Provider(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>;
}

You'll want create some way of calling this connect method, such as a "connect" wallet button with some click handler.

src/app/page.js
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>
);
}

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.

src/app/page.js
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>
);
}

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.

src/app/page.js
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>
);
}

Putting it all together

Here is the final code from above, all in one place.

src/app/page.js
"use client";

import { Database } from "@tableland/sdk";
import { providers } from "ethers";
import { useState } from "react";

async function connectSigner() {
// Establish a connection with the browser wallet's provider.
const provider = new providers.Web3Provider(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>
);
}

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.