Skip to main content

Add Tigris to an existing Next.js Application

In this guide we will take an existing Next.js blog-starter application. This application is static in nature. We will make it dynamic by adding comments feature that will use Tigris for data storage and retrieval.

add_to_existing_app_result

Prerequisites

System requirements

Sign up

To get started, go to the Tigris console and sign up.

Create Next.js app using blog-starter template

Then, create a new Next.js application using the blog-starter template

npx create-next-app@latest --example blog-starter
Terminal
$ npx create-next-app@latest --example blog-starter
Need to install the following packages:
create-next-app@13.0.7
Ok to proceed? (y) y
✔ What is your project named? … my-nextjs-blog
Creating a new Next.js app in <Current directory>/my-nextjs-blog.

Downloading files for example blog-starter. This might take a moment.

Installing packages. This might take a couple of minutes.


added 180 packages, and audited 181 packages in 13s

82 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities

Initialized a git repository.

Success! Created my-nextjs-blog at <Current directory>/my-nextjs-blog
Inside that directory, you can run several commands:

npm run dev
Starts the development server.

npm run build
Builds the app for production.

npm start
Runs the built app in production mode.

We suggest that you begin by typing:

cd my-nextjs-blog
npm run dev

This is a simple command to quickly start building a new Next.js application based off a template. It may ask you a project name to download the project resources. We choose my-nextjs-blog as our project name.

Change to my-nextjs-blog directory and install the Tigris SDK:

cd my-nextjs-blog
npm install @tigrisdata/core@latest --save

Create Tigris Project

Now let's create a Tigris project for our blogging application. Click on the Create a new project button from the

Tigris Cloud dashboard.

The create a project dialog within the Tigris console web application

Use MyNextjsBlog as the project name.

Tigris will generate the credentials for your application, and you will be shown a command to generate application code with Tigris pre-configured.

The create-tigirs-app terminal command displayed within a dialog within the Tigris web console

As we are not creating a new application, rather adding Tigris to an existing application, we don't need to run this command.

Let's copy the project name, clientId and clientSecret from the command shown and close the modal.

Configure environment variables

Now that we have created the project, lets set up the environment variables which will allow the blog application to connect with Tigris.

We will use the project name, clientId and clientSecret from the previous step

.env.local
TIGRIS_URI=api.preview.tigrisdata.cloud
TIGRIS_PROJECT=MyNextjsBlog
TIGRIS_CLIENT_ID=ftSUj9B5czFW79s9M6YUkxKE3H4WeRyY
TIGRIS_CLIENT_SECRET=DOxxx
TIGRIS_DB_BRANCH=main

Configure tsconfig.json

Let's ensure that the required settings are enabled in tsconfig.json.

tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "es5",
"module": "esnext",
"jsx": "preserve",
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"noEmit": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true
},
"ts-node": {
"compilerOptions": {
"module": "commonjs"
}
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}

Create a Tigris Collection

Every Tigris project comes with a pre-configured database and stores data records as documents. Documents are analogous to JSON objects grouped in a Collection.

Let's create a data model to define the collection. We will be storing the data models in the directory db/models.

db/models/comments.ts
import {
Field,
PrimaryKey,
TigrisCollection,
TigrisDataTypes,
} from "@tigrisdata/core";

@TigrisCollection("comments")
export class Comments {
@PrimaryKey(TigrisDataTypes.INT32, { order: 1, autoGenerate: true })
id!: number;

@Field()
name: string;

@Field()
content: string;

@Field(TigrisDataTypes.DATE_TIME)
date: string;
}

Let's see what is going on here. You've imported the Tigris @TigrisCollection decorator, which accepts a collection name as an argument to create a collection. The TigrisDataTypes type gives you access to the Tigris data types. @Field decorator defines the collection fields and the @PrimaryKey decorator define the primary of the collection.

Create a setup script

Now we will create a setup script which will take care of creating the collection for the data model defined above.

setup.ts
import { loadEnvConfig } from "@next/env";
import { Tigris } from "@tigrisdata/core";
import { Comments } from "./db/models/comments";

async function main() {
// load environment variables
loadEnvConfig(process.cwd());

// create collections
const tigrisClient = new Tigris();
await tigrisClient.registerSchemas([Comments]);
}

main()
.then(async () => {
console.log("Setup complete ...");
process.exit(0);
})
.catch(async (e) => {
console.error(e);
process.exit(1);
});

Here we have used the registerSchemas method, which takes in an array of all the model classes defined in your db/model directory. In our case we have a single model class Comments. When this script is executed Tigris will create a collection named comments corresponding to the model class Comments.

Next, let's add setup.ts as one of the scripts in package.json.

package.json
  "scripts": {
"predev": "npm run setup",
"dev": "next",
"build": "next build",
"postbuild": "npm run setup",
"setup": "npx ts-node setup.ts",
"start": "next start",
"typecheck": "tsc"
}

Now, whenever you run your application your collections will always be in sync with the data models defined in the application.

Instantiate the Tigris client

Next, initialize the Tigris client and export it to share it across all the modules.

lib/tigris.ts
import { DB, Tigris } from "@tigrisdata/core";

const tigrisClient = new Tigris();
const tigrisDB: DB = tigrisClient.getDatabase();

// export to share DB across modules
export default tigrisDB;

Create CRUD Operations

You can now create and execute CRUD operations against the comments collection. These endpoints will be used by the client component of your application to perform CRUD operations in Tigris. Create a api/comments/index.ts file in the pages directory to define your API endpoints.

⌲ GET /api/comments to fetch all blogs.

pages/api/comments/index.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { Comments } from "../../../db/models/comments";
import tigrisDB from "../../../lib/tigris";

type Response = {
result?: Array<Comments>;
error?: string;
};

async function handleGet(req: NextApiRequest, res: NextApiResponse<Response>) {
try {
const commentsCollection = tigrisDB.getCollection<Comments>(Comments);
const cursor = commentsCollection.findMany();
const comments = await cursor.toArray();
res.status(200).json({ result: comments });
} catch (err) {
res.status(500).json({ error: err.message });
}
}

Here we used a simple findMany() function to read documents from a Tigris collection and returned them as json.

⌲ POST /api/comments to add a new blog data.

Next, create a POST handler in the same file pages/api/comments/index.ts where we added the GET handler above. This will store a new comment in the Comments collection.

pages/api/comments/index.ts
//...
async function handlePost(req: NextApiRequest, res: NextApiResponse<Response>) {
try {
const comment = JSON.parse(req.body) as Comments;
const commentsCollection = tigrisDB.getCollection<Comments>(Comments);
const inserted = await commentsCollection.insertOne(comment);
res.status(200).json({ result: [inserted] });
} catch (err) {
res.status(500).json({ error: err.message });
}
}
//...

Finally, add the handler function to define the routes for the API endpoints:

pages/api/comments/index.ts
//...
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`);
}
}
//...

⌲ GET /api/comments/search to find a comment matching the given query

The search endpoint will allow users to find comments in the collection. Let's add this endpoint that accepts a query parameter and responds with matching results. Eg. GET /api/comments/search?q="query"

Create a search.ts file in the pages/api/comments directory and add the code:

pages/api/comments/search.ts
import { NextApiRequest, NextApiResponse } from "next";
import { Comments } from "../../../db/models/comments";
import { SearchRequest } from "@tigrisdata/core/dist/search/types";
import tigrisDb from "../../../lib/tigris";

type Data = {
result?: Array<Comments>;
error?: string;
};

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 commentCollection = tigrisDb.getCollection<Comments>(Comments);
const searchRequest: SearchRequest<Comments> = { q: query as string };
const searchResult = await commentCollection.search(searchRequest);
const comment = new Array<Comments>();
for (const hit of searchResult.hits) {
comment.push(hit.document);
}
res.status(200).json({ result: comment });
} catch (err) {
res.status(500).json({ error: err.message });
}
}
Built-in search

Tigris makes it really easy to implement search within your applications by providing a serverless full-text search engine that makes all your data instantly searchable.

Use in the components

Now you can request the API to fetch and manipulate the data from your Tigris database in clients components. First, create comment.tsx and each-comments.tsx files in the components directory to create the components for the comments.

Inside the components/each-comments.tsx file, add the following code:

components/each-comments.tsx
import { Comments } from "../db/models/comments";

type Props = {
comment: Comments;
};
const EachComments = ({ comment }: Props) => {
return (
<div className="relative grid grid-cols-1 gap-4 p-4 mb-8 border rounded-lg bg-white shadow-lg">
<div className="relative flex gap-4">
<img
src="https://icons.iconarchive.com/icons/diversity-avatars/avatars/256/charlie-chaplin-icon.png"
className="relative rounded-lg -top-8 -mb-4 bg-white border h-20 w-20"
alt=""
loading="lazy"
/>
<div className="flex flex-col w-full">
<div className="flex flex-row justify-between">
<p className="relative text-xl whitespace-nowrap truncate overflow-hidden">
{comment.content}
</p>
<a className="text-gray-500 text-xl" href="#">
<i className="fa-solid fa-trash"></i>
</a>
</div>
<p className="text-gray-400 text-sm">{comment.date}</p>
</div>
</div>
<p className="-mt-4 text-gray-500">{comment.name}</p>
</div>
);
};

export default EachComments;

Next, let's define the React component that is rendered on the browser using the API endpoints we created in the previous section. Add the code below to the components/comment.tsx file:

components/comment.tsx
import { useEffect, useState } from "react";
import { Comments } from "../db/models/comments";
import EachComments from "./each-comments";

const Comment = () => {
const [comments, setComments] = useState<Comments[]>([]);
const [isError, setIsError] = useState(false);
const [nameInput, setNameInput] = useState("");
const [commentInput, setCommentInput] = useState("");
const [searchInput, setSearchInput] = useState("");

// fetch list of comments from `/api/comments` endpoint which in turn
// reads from the comments collection
const fetchComments = () => {
fetch("/api/comments")
.then((response) => response.json())
.then((data) => {
if (data.result) {
setComments(data.result);
} else {
setIsError(true);
}
})
.catch(() => {
setIsError(true);
});
};

// create a new comment by sending a POST request to `/api/comments` endpoint
const addComment = () => {
fetch("/api/comments", {
method: "POST",
body: JSON.stringify({
name: nameInput,
content: commentInput,
date: new Date().toJSON(),
}),
}).then(() => {
setCommentInput("");
setNameInput("");
fetchComments();
});
};

// query the collection for a comments by sending a GET request to
// the `/api/comments/search` endpoint
const searchQuery = () => {
fetch(`/api/comments/search?q=${encodeURI(searchInput)}`, {
method: "GET",
})
.then((response) => response.json())
.then((data) => {
if (data.result) {
setComments(data.result);
}
});
};

useEffect(() => {
fetchComments();
}, []);

// render the comments and add the functions defined above to their
// respective event handlers
return (
<div className="max-w-2xl mx-auto">
<input
className="border border-gray-300 text-gray-900 dark:text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500 mr-2 mb-6"
placeholder="Search..."
onChange={(e) => setSearchInput(e.target.value)}
onKeyUp={searchQuery}
/>

{comments.length ? (
comments.map((comment) => (
<EachComments comment={comment} key={comment.id} />
))
) : (
<p className="relative grid grid-cols-1 gap-4 p-4 mb-8 border rounded-lg bg-white shadow-lg">
No comments...
</p>
)}
<h3>Leave a comment</h3>
<div className="mb-6 mt-6">
<input
className="border border-gray-300 text-gray-900 dark:text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Name:"
onChange={(e) => setNameInput(e.target.value)}
value={nameInput}
/>
</div>
<div className="mb-6 mt-6">
<textarea
className="border border-gray-300 text-gray-900 dark:text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:border-gray-600 dark:placeholder-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Say something..."
onChange={(e) => setCommentInput(e.target.value)}
value={commentInput}
/>
</div>
<button
type="submit"
className="bg-gray-600 hover:bg-gray-800 focus:ring-4 focus:ring-blue-300 font-medium text-white rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center mb-3"
onClick={addComment}
>
Post comment
</button>
{isError && (
<p className="text-red-600">Something went wrong: {isError}</p>
)}
</div>
);
};

export default Comment;

Lastly, render the <Comment/> component inside the <Container> component in the pages/posts/[slug].tsx file:

pages/posts/[slug].tsx
//...
import Comment from '../../components/comment'

//...
export default function Post({ ... }: ... {

return (
<Layout preview={preview}>
<Container>
//...
<Comment/>
</Container>
</Layout>
)
}
//...

Run the application

Our blog application is now ready with the shiny new comments functionality. Let's run the application.

npm run dev
Terminal
$ npm run dev

> predev
> npm run setup


> setup
> npx ts-node setup.ts

info - Using reflection to infer type of Comments#name
info - Using reflection to infer type of Comments#content
Loaded env from <Current Directory>/projects/my-nextjs-blog/.env.local
info - Using Tigris at: api.preview.tigrisdata.cloud:443
event - Creating collection: 'comments' in project: 'MyNextjsBlog'
Setup complete ...

> dev
> next

ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info - Loaded env from <Current Directory>/projects/my-nextjs-blog/.env.local
event - compiled client and server successfully in 302 ms (165 modules)
wait - compiling / (client and server)...
event - compiled client and server successfully in 1341 ms (581 modules)

This should startup the application and make it available on http://localhost:3000

blog-starter application running in dev

Head over to one of the blog posts and you will see the comments form.

blog-starter application post with comments form

Now, if you go back to your Tigris Cloud console, you will see the collection for the project and as you will add the comments you will see the data show up.