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
.