Next.js gives you the best developer experience with all the features you need to build modern, fast production-ready applications. Tigris is the perfect companion for Next.js as it is similarly built with developer experience in mind and is truly serverless: build data-rich features, seamlessly implement search, and easily use it with serverless functions, all without needing to do Ops.
Now with the introduction out of the way, it is time to demonstrate how.
This is the first of a series of blog posts where we will demonstrate how easy it is to build Next.js apps with Tigris. We will build a to-do list app and deploy it to Vercel. The to-do list app will have the following features:
- add to-do items to the list
- update to-do items as completed
- delete to-do items
- search for to-do items in the list
To follow along with this tutorial you can get the code from the GitHub repo. This is how the to-do app will look once its deployed: https://tigris-nextjs-starter-kit.vercel.app/
Prerequisitesโ
For this tutorial you'll need:
- GitHub account (sign up for free)
- Tigris Cloud account (sign up for free)
- Vercel account (sign up for free) to deploy app
- Node.js 16+
- npm and npx
Deploying the to-do list app to Vercelโ
We will start off first by deploying the pre-prepared to-do list app to Vercel from the GitHub repo. Then once it is deployed and running, we will explore the code in detail.
Create a project on Vercelโ
Vercel makes it easier to deploy Git projects with a few clicks.
Hit the following Deploy button to get started with the Vercel workflow to clone the repo to your account
This should take you to Vercel to the "Create Git Repository" step
Add Tigris integrationโ
Pick a name for your new Git repo and then you'll configure the Tigris
integration that will setup the environment variables needed to connect to
Tigris: TIGRIS_PROJECT
,TIGRIS_URI
, TIGRIS_CLIENT_ID
, TIGRIS_CLIENT_SECRET
and TIGRIS_DB_BRANCH
.
Hit the Add button and it will take you to the Tigris integration page where in few simple steps you will be able to configure the integration.
Hit the Continue button and that's it!
Once the deployment completes, continue to your project dashboard on Vercel where you'll find URL for your to-do list app
๐ All done. Visit the URL in browser to access your to-do list app and play around. ๐
Now let's continue to explore the code for the to-do list app to see how easily Tigris can be integrated with Next.js.
Code walk-throughโ
This section will elaborate on important aspects of the to-do list app you just deployed. Let's glance over the important components of the project.
|-- package.json
|-- lib
|-- tigris.ts
|-- db
|-- models
|-- todoItems.ts
|-- pages
|-- index.tsx
|-- api
|-- item
|-- [id].ts
|-- items
|-- index.ts
|-- search.ts
Tigris data models and collections - db/models
โ
With Tigris it all starts with the data model! Tigris stores data records as documents. Documents are analogous to JSON objects but Tigris stores them in an optimized binary format. Documents are grouped together in collections.
The to-do list app has a single collection
todoItems
that stores the to-do items. The first thing you would do is
define the schema.
Tigris follows the convention of having the model stored in the
db/models
directory. Within this directory we have the
todoItems.ts
file, which defines the model named TodoItem
and the collection of those models called todoItems
:
import {
Field,
PrimaryKey,
TigrisCollection,
TigrisDataTypes,
} from "@tigrisdata/core";
@TigrisCollection("todoItems")
export class TodoItem {
@PrimaryKey(TigrisDataTypes.INT32, { order: 1, autoGenerate: true })
id!: number;
@Field()
text: string;
@Field()
completed: string;
}
Connecting to Tigris - lib/tigris.ts
โ
This file loads the environment variables that are populated by the Tigris integration, initializes the Tigris client, and exports it to be shared among all modules.
import { Tigris } from "@tigrisdata/core";
const tigrisClient = new Tigris();
const tigrisDb = tigrisClient.getDatabase();
// export to share DB across modules
export default tigrisDb;
Creating the database and collection - scripts/setup.ts
โ
The file scripts/setup.ts
sets up the database and collection in the registerSchemas
array automatically at build time. It also sets up a branch for your database.
import { Tigris } from "@tigrisdata/core";
import { TodoItem } from "../db/models/todoItems";
async function main() {
// setup client
const tigrisClient = new Tigris();
// ensure branch exists, create it if it needs to be created dynamically
await tigrisClient.getDatabase().initializeBranch();
// register schemas
await tigrisClient.registerSchemas([TodoItem]);
}
main();
Pagesโ
Let's take a look at fetchListItems()
in this React component that
loads and renders the to-do list items.
// Fetch Todo List
const fetchListItems = () => {
setFetchStatus("loading");
fetch("/api/items")
.then((response) => response.json())
.then((data) => {
setFetchStatus("success");
if (data.result) {
setViewMode("list");
setTodoList(data.result);
} else {
setFetchStatus("error");
}
})
.catch(() => {
setFetchStatus("error");
});
};
Evidently this React component is only rendering the items returned by
/api/items
.
Similarly, the addTodoItem()
, to add a to-do list item, simply
makes a POST request to /api/items
.
// Add a new to-do item
const addToDoItem = () => {
if (queryCheckWiggle()) {
return;
}
setFetchStatus("loading");
fetch("/api/items", {
method: "POST",
body: JSON.stringify({ text: textInput, completed: false }),
}).then(() => {
setFetchStatus("success");
setTextInput("");
fetchListItems();
});
};
We will now dive into the API routes to see how these are integrated with Tigris that is powering our application.
All the API routes are deployed as Serverless Functions. Tigris is serverless itself and natively supports HTTP. This makes it a perfect fit for Serverless Functions.
API routes to find and add itemsโ
All the Next.js API routes are defined under /pages/api/
. We have three files:
/pages/api/items/index.ts
, /pages/api/items/search.ts
and
/pages/api/item/[id].ts
that expose following endpoints:
GET /api/items
to get an array of to-do items asArray<TodoItem>
POST /api/items
to add an item to the listGET /api/items/search?q=query
to find and return items matching the given queryGET /api/item/{id}
to fetch an itemPUT /api/item/{id}
to update the given itemDELETE /api/item/[id]
to delete an item
Let's look at the /api/items
api that supports both GET
and POST
handlers.
import type { NextApiRequest, NextApiResponse } from 'next';
import { TodoItem } from '../../../db/models/todoItems';
import tigrisDb from '../../../lib/tigris';
type Response = {
result?: Array<TodoItem>
error?: string
};
// GET /api/items -- gets items from collection
// POST /api/items {ToDoItem} -- inserts a new item to collection
export default async function handler (
req: NextApiRequest,
res: NextApiResponse<Response>
) {
switch (req.method) {
case 'GET':
await handleGet(req, res);
break
case 'POST':
await handlePost(req, res);
break
default:
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
...
handleGet()
method is fetching and returning items from Tigris collection,
let's take a look at its implementation. You will see how easy it is to
fetch data from Tigris.
...
async function handleGet(req: NextApiRequest, res: NextApiResponse<Response>) {
try {
const itemsCollection = tigrisDb.getCollection<TodoItem>(TodoItem);
const cursor = itemsCollection.findMany();
const items = await cursor.toArray();
res.status(200).json({ result: items });
} catch (err) {
const error = err as Error;
res.status(500).json({ error: error.message });
}
}
...
The itemsCollection.findMany()
function sends a query to Tigris and
returns a cursor to fetch results from collection.
Let's look at handlePost()
implementation that inserts a TodoItem
in
collection by using the insertOne()
function.
...
async function handlePost(req: NextApiRequest, res: NextApiResponse<Response>) {
try {
const item = JSON.parse(req.body) as TodoItem;
const itemsCollection = tigrisDb.getCollection<TodoItem>(TodoItem);
const inserted = await itemsCollection.insertOne(item);
res.status(200).json({ result: [inserted] });
} catch (err) {
const error = err as Error;
res.status(500).json({ error: error.message });
}
}
API route to search itemsโ
Tigris makes it really easy to implement search within your applications by providing an embedded search engine that makes all your data instantly searchable.
Let's take a look at the search handler to see how easy it is to add
powerful real-time search functionality.
The itemsCollection.search
functions sends a search query to Tigris and
fetches the documents that match the query.
Note how you did not have to setup Elasticsearch, or configure search indexes. It was all taken care for you automatically.
import { NextApiRequest, NextApiResponse } from "next";
import { TodoItem } from "../../../db/models/todoItems";
import tigrisDb from "../../../lib/tigris";
type Data = {
result?: Array<TodoItem>;
error?: string;
};
// GET /api/items/search?q=searchQ -- searches for items matching text `searchQ`
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const query = req.query["q"];
if (query === undefined) {
res.status(400).json({ error: "No search query found in request" });
return;
}
try {
const itemsCollection = tigrisDb.getCollection<TodoItem>(TodoItem);
const searchResult = await itemsCollection.search({ q: query as string });
const items = new Array<TodoItem>();
for await (const res of searchResult) {
res.hits.forEach((hit) => items.push(hit.document));
}
res.status(200).json({ result: items });
} catch (err) {
const error = err as Error;
res.status(500).json({ error: error.message });
}
}
Summaryโ
In this tutorial, you deployed a to-do list Next.js app that uses Tigris as the backend. You saw all the powerful functionality that Tigris provides, and how easy it is to use it within Serverless Functions.
Tigris is the easiest way to work with data in your Next.js applications. Tigris and Next.js provide developers with the fastest way to build fast, data-rich and highly-responsive applications.
You can find the complete source code for this tutorial GitHub repo, feel free to raise issues or contribute to the project.
Happy learning!
Tigris is the data platform built for Next.js applications! Use it as a scalable, ACID transactional, real-time backend for your serverless applications. Build rich features with dynamic data without worrying about slow database queries or missing indexes. Seamlessly implement search within your applications with its embedded search engine. Connect serverless functions with its event streams to build highly responsive applications that scale automatically.
