diff --git a/examples/with-tigris/.env.development b/examples/with-tigris/.env.development
new file mode 100644
index 0000000000..2035afbfb9
--- /dev/null
+++ b/examples/with-tigris/.env.development
@@ -0,0 +1,4 @@
+# DO NOT ADD SECRETS TO THIS FILE. This is a good place for defaults.
+# If you want to add secrets use `.env.development.local` instead.
+
+TIGRIS_URI=localhost:8081
diff --git a/examples/with-tigris/.env.local.example b/examples/with-tigris/.env.local.example
new file mode 100644
index 0000000000..d020d0b248
--- /dev/null
+++ b/examples/with-tigris/.env.local.example
@@ -0,0 +1,7 @@
+# Enter your tigris uri, ex :- localhost:8081, api.preview.tigrisdata.cloud etc.
+TIGRIS_URI=
+
+# Client credentials, if using auth, can be generated from Tigris cloud console.
+# See: https://docs.tigrisdata.com/auth
+TIGRIS_CLIENT_ID=
+TIGRIS_CLIENT_SECRET=
diff --git a/examples/with-tigris/.env.production b/examples/with-tigris/.env.production
new file mode 100644
index 0000000000..b4b9c02637
--- /dev/null
+++ b/examples/with-tigris/.env.production
@@ -0,0 +1,4 @@
+# DO NOT ADD SECRETS TO THIS FILE. This is a good place for defaults.
+# If you want to add secrets use `.env.production.local` instead.
+
+TIGRIS_URI=api.preview.tigrisdata.cloud
diff --git a/examples/with-tigris/.gitignore b/examples/with-tigris/.gitignore
new file mode 100644
index 0000000000..b0167ec862
--- /dev/null
+++ b/examples/with-tigris/.gitignore
@@ -0,0 +1,27 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+# testing
+/coverage
+# next.js
+/.next/
+/out/
+# production
+/build
+# misc
+.DS_Store
+*.pem
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+# local env files
+.env*.local
+# vercel
+.vercel
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/examples/with-tigris/README.md b/examples/with-tigris/README.md
new file mode 100644
index 0000000000..58d9abdd15
--- /dev/null
+++ b/examples/with-tigris/README.md
@@ -0,0 +1,171 @@
+# β‘ οΈTigris example app on Next.js - Todo list
+
+A simple todo app built on [Next.js][next-url] and [Tigris](https://docs.tigrisdata.com/)
+using [TypeScript client](https://docs.tigrisdata.com/typescript/), deployed on [Vercel][vercel-url].
+
+### Project demo
+
+https://tigris-nextjs-starter-kit.vercel.app/
+
+# βοΈ Deploying your own
+
+All you need is a [Github](https://github.com), [Vercel][vercel-url] and Tigris
+account([sign up for a free account](https://www.tigrisdata.com/nextjs#signup-form)). Now, Hit "Deploy"
+and follow instructions to deploy app to your Vercel account
+
+[![Deploy with Vercel](https://vercel.com/button)][deploy-url]
+
+:tada: All done. You should be able to use app on the URL provided by Vercel. Feel free to play around
+or do a [code walkthrough](#code-walkthrough) next :tada:
+
+> [Tigris integration](https://vercel.com/integrations/tigris) with Vercel will automatically fetch
+> access keys to populate [Environment Variables](.env.local.example) when deploying app.
+
+
+2. Running Next.js server & Tigris dev environment on your local computer
+
+## π Running Next.js server & Tigris locally
+
+### Prerequisites
+
+1. Tigris installed on your dev computer
+ 1. For **macOS**: `brew install tigrisdata/tigris/tigris-cli`
+ 2. Other operating systems: [See installation instructions here](https://docs.tigrisdata.com/cli/installation)
+2. Node.js version 16+
+
+### Instructions
+
+1. Clone this repo on your computer
+
+```shell
+git clone https://github.com/tigrisdata/tigris-vercel-starter
+```
+
+2. Install dependencies
+
+```shell
+cd tigris-vercel-starter
+npm install
+```
+
+3. Start Tigris local development environment
+
+```shell
+tigris dev start
+```
+
+4. Run the Next.js server
+
+```shell
+npm run dev
+```
+
+> Note: This step uses a custom dev & build script to initialize Tigris database and collection for
+> the app and requires [ts-node](https://www.npmjs.com/package/ts-node#installation) to be installed.
+
+:tada: All done. You should be able to use app on `localhost:3000` in browser. Feel free to play
+around or do a [code walk-through](#code-walkthrough) next :tada:
+
+
+
+# π How to use
+
+Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:
+
+```bash
+npx create-next-app --example with-tigris tigris-next-app
+```
+
+```bash
+yarn create next-app --example with-tigris tigris-next-app
+```
+
+```bash
+pnpm create next-app --example with-tigris tigris-next-app
+```
+
+Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
+
+# π Code walkthrough
+
+
+ π File structure
+
+```text
+βββ package.json
+βββ lib
+β βββ tigris.ts
+βββ models
+β βββ tigris
+β βββ todoStarterApp
+β βββ todoItems.ts
+βββ pages
+ βββ index.tsx
+ βββ api
+ βββ item
+ β βββ [id].ts
+ βββ items
+ βββ index.ts
+ βββ search.ts
+```
+
+
+
+
+ πͺ’ Tigris schema definition
+
+[models/tigris/todoStarterApp/todoItems.ts](models/tigris/todoStarterApp/todoItems.ts) - The to-do list app
+has a single collection `todoItems` that stores the to-do items in `todoStarterApp` database. The
+Database and Collection get automatically provisioned by the [setup script](scripts/setup.ts).
+
+This is an inspiration from Next.js based file system router. Create a folder or drop a schema file
+inside database folder under `models/tigris/`, and you're able to instantly create Databases and
+Collections in Tigris for your application.
+
+
+
+
+ π Connecting to Tigris
+
+[lib/tigris.ts](lib/tigris.ts) - Loads the environment variables you specified previously in creating a Vercel project
+section and uses them to configure the Tigris client.
+
+
+
+
+ βοΈ API routes to access data in Tigris collection
+
+All the Next.js API routes are defined under `pages/api/`. We have three files exposing endpoints:
+
+#### [`pages/api/items/index.ts`](pages/api/items/index.ts)
+
+- `GET /api/items` to get an array of to-do items as Array
+- `POST /api/items` to add an item to the list
+
+#### [`/pages/api/items/search.ts`](/pages/api/items/search.ts)
+
+- `GET /api/items/search?q=query` to find and return items matching the given query
+
+#### [`pages/api/item/[id].ts`](pages/api/item/[id].ts)
+
+- `GET /api/item/{id}` to fetch an item
+- `PUT /api/item/{id}` to update the given item
+- `DELETE /api/item/[id]` to delete an item
+
+
+
+# π Next steps
+
+In a few steps, we learnt how to bootstrap a Next.js app using Tigris and deploy it on Vercel. Feel
+free to add more functionalities or customize App for your use-case and learn more about
+[Tigris data platform](https://docs.tigrisdata.com/overview/)
+
+
+
+[typescript]: https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white
+[typescript-url]: https://www.typescriptlang.org/
+[vercel]: https://img.shields.io/badge/vercel-F22F46?style=for-the-badge&logo=vercel&logoColor=white
+[vercel-url]: https://vercel.com/
+[deploy-url]: https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Ftigrisdata%2Ftigris-vercel-starter&project-name=todo-list-app-tigris&repo-name=todo-list-webapp-tigris&demo-title=My%20To-do%20list%20webapp&demo-description=A%20To-do%20list%20webapp%20using%20NextJS%20and%20Tigris&integration-ids=oac_Orjx197uMuJobdSaEpVv2Zn8
+[next.js]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white
+[next-url]: https://nextjs.org/
diff --git a/examples/with-tigris/components/EachToDo.tsx b/examples/with-tigris/components/EachToDo.tsx
new file mode 100644
index 0000000000..2409a68f4a
--- /dev/null
+++ b/examples/with-tigris/components/EachToDo.tsx
@@ -0,0 +1,47 @@
+import Image from 'next/image'
+import React from 'react'
+import { TodoItem } from '../models/tigris/todoStarterApp/todoItems'
+import styles from '../styles/EachToDo.module.css'
+
+type Props = {
+ toDoItem: TodoItem
+ deleteHandler: (id?: number) => void
+ updateHandler: (item: TodoItem) => void
+}
+const EachTodo = ({ toDoItem, deleteHandler, updateHandler }: Props) => {
+ return (
+ <>
+
+ {
+ updateHandler(toDoItem)
+ }}
+ >
+
+
+ {toDoItem.text}
+
+
+ {
+ deleteHandler(toDoItem.id)
+ }}
+ >
+
+
+
+ >
+ )
+}
+
+export default EachTodo
diff --git a/examples/with-tigris/components/LoaderWave.tsx b/examples/with-tigris/components/LoaderWave.tsx
new file mode 100644
index 0000000000..4e9e0ceb5d
--- /dev/null
+++ b/examples/with-tigris/components/LoaderWave.tsx
@@ -0,0 +1,14 @@
+import React from 'react'
+import styles from '../styles/LoaderWave.module.css'
+
+const LoaderWave = () => {
+ return (
+
+ )
+}
+
+export default LoaderWave
diff --git a/examples/with-tigris/lib/tigris.ts b/examples/with-tigris/lib/tigris.ts
new file mode 100644
index 0000000000..365739f876
--- /dev/null
+++ b/examples/with-tigris/lib/tigris.ts
@@ -0,0 +1,28 @@
+import { DB, Tigris } from '@tigrisdata/core'
+
+const DB_NAME = 'todoStarterApp'
+
+declare global {
+ // eslint-disable-next-line no-var
+ var tigrisDb: DB
+}
+
+let tigrisDb: DB
+
+// Caching the client because `next dev` would otherwise create a
+// new connection on every file save while previous connection is active due to
+// hot reloading. However, in production, Next.js would completely tear down before
+// restarting, thus, disconnecting and reconnecting to Tigris.
+if (process.env.NODE_ENV !== 'production') {
+ if (!global.tigrisDb) {
+ const tigrisClient = new Tigris()
+ global.tigrisDb = tigrisClient.getDatabase(DB_NAME)
+ }
+ tigrisDb = global.tigrisDb
+} else {
+ const tigrisClient = new Tigris()
+ tigrisDb = tigrisClient.getDatabase(DB_NAME)
+}
+
+// export to share DB across modules
+export default tigrisDb
diff --git a/examples/with-tigris/models/tigris/todoStarterApp/todoItems.ts b/examples/with-tigris/models/tigris/todoStarterApp/todoItems.ts
new file mode 100644
index 0000000000..9953fe98f6
--- /dev/null
+++ b/examples/with-tigris/models/tigris/todoStarterApp/todoItems.ts
@@ -0,0 +1,22 @@
+import {
+ TigrisCollectionType,
+ TigrisDataTypes,
+ TigrisSchema,
+} from '@tigrisdata/core/dist/types'
+
+export const COLLECTION_NAME = 'todoItems'
+
+export interface TodoItem extends TigrisCollectionType {
+ id?: number
+ text: string
+ completed: boolean
+}
+
+export const TodoItemSchema: TigrisSchema = {
+ id: {
+ type: TigrisDataTypes.INT32,
+ primary_key: { order: 1, autoGenerate: true },
+ },
+ text: { type: TigrisDataTypes.STRING },
+ completed: { type: TigrisDataTypes.BOOLEAN },
+}
diff --git a/examples/with-tigris/next.config.js b/examples/with-tigris/next.config.js
new file mode 100644
index 0000000000..897aacd834
--- /dev/null
+++ b/examples/with-tigris/next.config.js
@@ -0,0 +1,6 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ swcMinify: true,
+}
+
+module.exports = nextConfig
diff --git a/examples/with-tigris/package.json b/examples/with-tigris/package.json
new file mode 100644
index 0000000000..56eea50086
--- /dev/null
+++ b/examples/with-tigris/package.json
@@ -0,0 +1,23 @@
+{
+ "private": true,
+ "scripts": {
+ "predev": "APP_ENV=development npm run setup",
+ "dev": "next dev",
+ "build": "next build",
+ "postbuild": "APP_ENV=production npm run setup",
+ "start": "next start",
+ "setup": "npx ts-node scripts/setup.ts"
+ },
+ "dependencies": {
+ "@tigrisdata/core": "beta",
+ "next": "latest",
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ },
+ "devDependencies": {
+ "@types/node": "18.11.2",
+ "@types/react": "18.0.21",
+ "@types/react-dom": "18.0.6",
+ "typescript": "4.8.4"
+ }
+}
diff --git a/examples/with-tigris/pages/_app.tsx b/examples/with-tigris/pages/_app.tsx
new file mode 100644
index 0000000000..7a8af80e5b
--- /dev/null
+++ b/examples/with-tigris/pages/_app.tsx
@@ -0,0 +1,9 @@
+import '../styles/globals.css'
+import type { AppProps } from 'next/app'
+import React from 'react'
+
+function MyApp({ Component, pageProps }: AppProps) {
+ return
+}
+
+export default MyApp
diff --git a/examples/with-tigris/pages/api/item/[id].ts b/examples/with-tigris/pages/api/item/[id].ts
new file mode 100644
index 0000000000..6e5901053c
--- /dev/null
+++ b/examples/with-tigris/pages/api/item/[id].ts
@@ -0,0 +1,85 @@
+import { NextApiRequest, NextApiResponse } from 'next'
+import {
+ COLLECTION_NAME,
+ TodoItem,
+} from '../../../models/tigris/todoStarterApp/todoItems'
+import tigrisDb from '../../../lib/tigris'
+
+type Data = {
+ result?: TodoItem
+ error?: string
+}
+
+async function handleGet(
+ req: NextApiRequest,
+ res: NextApiResponse,
+ itemId: number
+) {
+ try {
+ const itemsCollection = tigrisDb.getCollection(COLLECTION_NAME)
+ const item = await itemsCollection.findOne({ id: itemId })
+ if (!item) {
+ res.status(404).json({ error: 'No item found' })
+ } else {
+ res.status(200).json({ result: item })
+ }
+ } catch (err) {
+ const error = err as Error
+ res.status(500).json({ error: error.message })
+ }
+}
+
+async function handlePut(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const item = JSON.parse(req.body) as TodoItem
+ const itemsCollection = tigrisDb.getCollection(COLLECTION_NAME)
+ const updated = await itemsCollection.insertOrReplaceOne(item)
+ res.status(200).json({ result: updated })
+ } catch (err) {
+ const error = err as Error
+ res.status(500).json({ error: error.message })
+ }
+}
+
+async function handleDelete(
+ req: NextApiRequest,
+ res: NextApiResponse,
+ itemId: number
+) {
+ try {
+ const itemsCollection = tigrisDb.getCollection(COLLECTION_NAME)
+ const status = (await itemsCollection.deleteOne({ id: itemId })).status
+ if (status === 'deleted') {
+ res.status(200).json({})
+ } else {
+ res.status(500).json({ error: `Failed to delete ${itemId}` })
+ }
+ } catch (err) {
+ const error = err as Error
+ res.status(500).json({ error: error.message })
+ }
+}
+
+// GET /api/item/[id] -- gets item from collection where id = [id]
+// PUT /api/item/[id] {ToDoItem} -- updates the item in collection where id = [id]
+// DELETE /api/item/[id] -- deletes the item in collection where id = [id]
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
+ const { id } = req.query
+ switch (req.method) {
+ case 'GET':
+ await handleGet(req, res, Number(id))
+ break
+ case 'PUT':
+ await handlePut(req, res)
+ break
+ case 'DELETE':
+ await handleDelete(req, res, Number(id))
+ break
+ default:
+ res.setHeader('Allow', ['GET', 'PUT', 'DELETE'])
+ res.status(405).end(`Method ${req.method} Not Allowed`)
+ }
+}
diff --git a/examples/with-tigris/pages/api/items/index.ts b/examples/with-tigris/pages/api/items/index.ts
new file mode 100644
index 0000000000..7f65b57824
--- /dev/null
+++ b/examples/with-tigris/pages/api/items/index.ts
@@ -0,0 +1,54 @@
+import type { NextApiRequest, NextApiResponse } from 'next'
+import {
+ COLLECTION_NAME,
+ TodoItem,
+} from '../../../models/tigris/todoStarterApp/todoItems'
+import tigrisDb from '../../../lib/tigris'
+
+type Response = {
+ result?: Array
+ error?: string
+}
+
+async function handleGet(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const itemsCollection = tigrisDb.getCollection(COLLECTION_NAME)
+ 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 })
+ }
+}
+
+async function handlePost(req: NextApiRequest, res: NextApiResponse) {
+ try {
+ const item = JSON.parse(req.body) as TodoItem
+ const itemsCollection = tigrisDb.getCollection(COLLECTION_NAME)
+ 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 })
+ }
+}
+
+// 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
+) {
+ 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`)
+ }
+}
diff --git a/examples/with-tigris/pages/api/items/search.ts b/examples/with-tigris/pages/api/items/search.ts
new file mode 100644
index 0000000000..e4e2dd0115
--- /dev/null
+++ b/examples/with-tigris/pages/api/items/search.ts
@@ -0,0 +1,37 @@
+import { NextApiRequest, NextApiResponse } from 'next'
+import {
+ COLLECTION_NAME,
+ TodoItem,
+} from '../../../models/tigris/todoStarterApp/todoItems'
+import { SearchRequest } from '@tigrisdata/core/dist/search/types'
+import tigrisDb from '../../../lib/tigris'
+
+type Data = {
+ result?: Array
+ error?: string
+}
+
+// GET /api/items/search?q=searchQ -- searches for items matching text `searchQ`
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
+ 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(COLLECTION_NAME)
+ const searchRequest: SearchRequest = { q: query as string }
+ const searchResult = await itemsCollection.search(searchRequest)
+ const items = new Array()
+ for (const hit of searchResult.hits) {
+ items.push(hit.document)
+ }
+ res.status(200).json({ result: items })
+ } catch (err) {
+ const error = err as Error
+ res.status(500).json({ error: error.message })
+ }
+}
diff --git a/examples/with-tigris/pages/index.tsx b/examples/with-tigris/pages/index.tsx
new file mode 100644
index 0000000000..689fbc9bcb
--- /dev/null
+++ b/examples/with-tigris/pages/index.tsx
@@ -0,0 +1,255 @@
+import type { NextPage } from 'next'
+import Image from 'next/image'
+import Head from 'next/head'
+import React, { useEffect, useState } from 'react'
+import EachTodo from '../components/EachToDo'
+import LoaderWave from '../components/LoaderWave'
+import { TodoItem } from '../models/tigris/todoStarterApp/todoItems'
+import styles from '../styles/Home.module.css'
+
+const Home: NextPage = () => {
+ // This is the input field
+ const [textInput, setTextInput] = useState('')
+
+ // Todo list array which displays the todo items
+ const [todoList, setTodoList] = useState([])
+
+ // Loading and Error flags for the template
+ const [isLoading, setIsLoading] = useState(false)
+ const [isError, setIsError] = useState(false)
+
+ // This is use to animate the input text field
+ const [wiggleError, setWiggleError] = useState(false)
+
+ // Two separate views. 1. List view for todo items & 2. Search result view
+ type viewModeType = 'list' | 'search'
+ const [viewMode, setViewMode] = useState('list')
+
+ // Util search query/input check
+ /*
+ The is a helper util method, that validtes the input field via a regex and returns a true or false.
+ This also wiggles the text input if the regex doesn't find any match.
+ */
+ const queryCheckWiggle = () => {
+ const result: RegExpMatchArray | null = textInput.match('^\\S.{0,100}$')
+ if (result === null) {
+ setWiggleError(true)
+ return true
+ }
+ return false
+ }
+
+ useEffect(() => {
+ if (!wiggleError) {
+ return
+ }
+ const timeOut = setTimeout(() => {
+ setWiggleError(false)
+ }, 500)
+
+ return () => clearTimeout(timeOut)
+ }, [wiggleError])
+
+ // Search query
+ /*
+ 'searchQuery' method takes the state from 'textInput' and send it over to the 'api/items/search' endpoint via a query param 'q'.
+ The response is the same as the response from "fetch('/api/items')", an array of TodoItems if successful.
+ */
+ const searchQuery = () => {
+ if (queryCheckWiggle()) {
+ return
+ }
+ setIsLoading(true)
+
+ fetch(`/api/items/search?q=${encodeURI(textInput)}`, {
+ method: 'GET',
+ })
+ .then((response) => response.json())
+ .then((data) => {
+ setIsLoading(false)
+ if (data.result) {
+ setViewMode('search')
+ setTodoList(data.result)
+ }
+ })
+ }
+
+ // Fetch Todo List
+ /*
+ 'fetchListItems' is the first method that's called when the component is mounted from the useEffect below.
+ This sets some of the state like 'isLoading' and 'isError' before it fetches for data from the endpoint defined under 'pages/api/items/index'.
+ The api endpoint returns a json with the key 'result' and a status 200 if successful or returns a status 500 along with the 'error' key.
+ If the 'result' key is present we safely set the 'todoList'.
+ */
+ const fetchListItems = () => {
+ setIsLoading(true)
+ setIsError(false)
+
+ fetch('/api/items')
+ .then((response) => response.json())
+ .then((data) => {
+ setIsLoading(false)
+ if (data.result) {
+ setViewMode('list')
+ setTodoList(data.result)
+ } else {
+ setIsError(true)
+ }
+ })
+ .catch(() => {
+ setIsLoading(false)
+ setIsError(true)
+ })
+ }
+
+ // Load the initial list of todo-items
+ useEffect(() => {
+ fetchListItems()
+ }, [])
+
+ // Add a new todo-item
+ /*
+ 'addToDoItem' takes the 'textInput' state, creates a 'TodoItem' & converts it to a JSON.
+ Sends it over as body payload to the api endpoint; which is how the api expects and is defined in 'pages/api/items' 'POST' switch.
+ */
+ const addToDoItem = () => {
+ if (queryCheckWiggle()) {
+ return
+ }
+ setIsLoading(true)
+
+ fetch('/api/items', {
+ method: 'POST',
+ body: JSON.stringify({ text: textInput, completed: false }),
+ }).then(() => {
+ setIsLoading(false)
+ setTextInput('')
+ fetchListItems()
+ })
+ }
+
+ // Delete Todo-item
+ /*
+ 'deleteTodoItem' requires an id value of the TodoItem. When the user presses the 'delete'(cross) button from a TodoItem, this method is invoked.
+ It calls the endpoint 'api/item/' with the 'DELETE' method. Read the method 'handleDelete' under pages/api/item/[id]' to learn more how the api handles deletion.
+ */
+ const deleteTodoItem = (id?: number) => {
+ setIsLoading(true)
+
+ fetch('/api/item/' + id, {
+ method: 'DELETE',
+ }).then(() => {
+ setIsLoading(false)
+ if (viewMode === 'list') {
+ fetchListItems()
+ } else {
+ searchQuery()
+ }
+ })
+ }
+
+ // Update Todo-item (mark complete/incomplete)
+ /*
+ 'updateTodoItem' takes the TodoItem object, inverts the 'completed' boolean and calls the same endpoint as 'deletion' but with a different method 'PUT'.
+ Navigate to 'api/item/[id]' and read more how the api handles updates under the 'handlePut' method.
+ */
+ const updateTodoItem = (item: TodoItem) => {
+ item.completed = !item.completed
+ setIsLoading(true)
+
+ fetch('/api/item/' + item.id, {
+ method: 'PUT',
+ body: JSON.stringify(item),
+ }).then(() => {
+ setIsLoading(false)
+ if (viewMode === 'list') {
+ fetchListItems()
+ } else {
+ searchQuery()
+ }
+ })
+ }
+
+ return (
+
+
+
Todo App using Next.js + Tigris
+
+
+
+
+
Sample Todo app using Next.js and Tigris
+
+ {/* Search Header */}
+
+ {
+ setWiggleError(false)
+ setTextInput(e.target.value)
+ }}
+ placeholder="Type an item to add or search"
+ />
+ Add
+ Search
+
+
+ {/* Results section */}
+
+ {/* Loader, Errors and Back to List mode */}
+ {isError && (
+
Something went wrong..
+ )}
+ {isLoading &&
}
+ {viewMode === 'search' && (
+
{
+ setTextInput('')
+ fetchListItems()
+ }}
+ >
+ Go back to list
+
+ )}
+
+ {/* Todo Item List */}
+ {todoList.length < 1 ? (
+
+ {viewMode === 'search'
+ ? 'No items found.. '
+ : 'Add a todo by typing in the field above and hit Add!'}
+
+ ) : (
+
+ {todoList.map((each) => {
+ return (
+
+ )
+ })}
+
+ )}
+
+
+
+
+
+
+
+ )
+}
+
+export default Home
diff --git a/examples/with-tigris/public/circle-checked.svg b/examples/with-tigris/public/circle-checked.svg
new file mode 100644
index 0000000000..b442799597
--- /dev/null
+++ b/examples/with-tigris/public/circle-checked.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/examples/with-tigris/public/circle.svg b/examples/with-tigris/public/circle.svg
new file mode 100644
index 0000000000..1fb66edd92
--- /dev/null
+++ b/examples/with-tigris/public/circle.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/examples/with-tigris/public/delete.svg b/examples/with-tigris/public/delete.svg
new file mode 100644
index 0000000000..8f293bca4d
--- /dev/null
+++ b/examples/with-tigris/public/delete.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/examples/with-tigris/public/favicon.ico b/examples/with-tigris/public/favicon.ico
new file mode 100644
index 0000000000..0097d88f2c
Binary files /dev/null and b/examples/with-tigris/public/favicon.ico differ
diff --git a/examples/with-tigris/public/readme/logo.svg b/examples/with-tigris/public/readme/logo.svg
new file mode 100755
index 0000000000..9fb56cdded
--- /dev/null
+++ b/examples/with-tigris/public/readme/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/with-tigris/public/readme/todo_app_screenshot.jpg b/examples/with-tigris/public/readme/todo_app_screenshot.jpg
new file mode 100644
index 0000000000..46e6817015
Binary files /dev/null and b/examples/with-tigris/public/readme/todo_app_screenshot.jpg differ
diff --git a/examples/with-tigris/public/tigris_logo.svg b/examples/with-tigris/public/tigris_logo.svg
new file mode 100644
index 0000000000..9a5e50e0fe
--- /dev/null
+++ b/examples/with-tigris/public/tigris_logo.svg
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/with-tigris/public/vercel.svg b/examples/with-tigris/public/vercel.svg
new file mode 100644
index 0000000000..fbf0e25a65
--- /dev/null
+++ b/examples/with-tigris/public/vercel.svg
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/examples/with-tigris/scripts/setup.ts b/examples/with-tigris/scripts/setup.ts
new file mode 100644
index 0000000000..42ad6b69d3
--- /dev/null
+++ b/examples/with-tigris/scripts/setup.ts
@@ -0,0 +1,19 @@
+import { Tigris } from '@tigrisdata/core'
+import { loadEnvConfig } from '@next/env'
+
+// Run the config loader only when not executing within next runtime
+if (process.env.NODE_ENV === undefined) {
+ if (process.env.APP_ENV === 'development') {
+ loadEnvConfig(process.cwd(), true)
+ } else if (process.env.APP_ENV === 'production') {
+ loadEnvConfig(process.cwd())
+ }
+}
+
+async function main() {
+ // setup client and register schemas
+ const tigrisClient = new Tigris()
+ await tigrisClient.registerSchemas('models/tigris')
+}
+
+main()
diff --git a/examples/with-tigris/styles/EachToDo.module.css b/examples/with-tigris/styles/EachToDo.module.css
new file mode 100644
index 0000000000..ed1556d2cd
--- /dev/null
+++ b/examples/with-tigris/styles/EachToDo.module.css
@@ -0,0 +1,58 @@
+.each {
+ display: flex;
+ flex-direction: row;
+ border-bottom: 1px solid #dddddd;
+ padding: 10px 25px;
+ animation: fadeInAnimation 0.4s cubic-bezier(0.62, 0.57, 0, 1.02);
+}
+.each:last-child {
+ border-bottom: none;
+}
+
+.eachButton {
+ background-color: transparent !important;
+ width: 100%;
+ text-align: left;
+ vertical-align: middle;
+ padding: 0 !important;
+}
+
+.eachButton:hover img {
+ content: url('/circle-checked.svg');
+}
+
+.eachButton:hover span {
+ text-decoration: line-through;
+}
+
+.each span {
+ vertical-align: middle;
+ padding-left: 10px;
+ font-size: 14px;
+}
+
+.deleteBtn {
+ background-color: transparent !important;
+ text-align: center !important;
+ min-width: 20px !important;
+ padding: 10px !important;
+}
+
+.deleteBtn:hover,
+.deleteBtn:focus,
+.deleteBtn:active,
+.deleteBtn:focus-visible {
+ filter: brightness(0) saturate(100%) invert(98%) sepia(27%) saturate(4410%)
+ hue-rotate(308deg) brightness(115%) contrast(98%);
+}
+
+@keyframes fadeInAnimation {
+ from {
+ opacity: 0;
+ transform: translateX(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: none;
+ }
+}
diff --git a/examples/with-tigris/styles/Home.module.css b/examples/with-tigris/styles/Home.module.css
new file mode 100644
index 0000000000..d6f108465e
--- /dev/null
+++ b/examples/with-tigris/styles/Home.module.css
@@ -0,0 +1,138 @@
+.container {
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+ margin-top: 50px;
+ justify-content: space-between;
+ align-content: space-around;
+}
+
+.searchHeader {
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
+ margin-top: 20px;
+}
+
+.searchInput {
+ min-width: 300px;
+}
+
+.results {
+ position: relative;
+ background-color: #fff;
+ min-width: 600px;
+ min-height: 200px;
+ border-radius: 8px;
+ color: #0f2832;
+ overflow: hidden;
+}
+
+.container input {
+ border: 1px solid #fff;
+ background-color: white;
+ padding: 8px 16px;
+ background-color: #fff;
+ border-radius: 8px;
+ border-color: #d9dbe1;
+ color: #0f2832;
+ width: 100%;
+ display: block;
+ height: 40px;
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 22px;
+ letter-spacing: 0px;
+ transition: background-color 0.2s, box-shadow 0.2s;
+ caret-color: #4ed9b2;
+}
+
+.container input:hover,
+.container input:focus,
+.container input:active,
+.container input:focus-visible {
+ border-color: #4ed9b2;
+ outline: #4ed9b2;
+}
+
+.container input:disabled {
+ pointer-events: none;
+ border: 1px solid #d9dbe1;
+ color: #a2a3a8;
+}
+
+.container button {
+ align-items: center;
+ border-radius: 8px;
+ color: #0f2832;
+ cursor: pointer;
+ font-size: 12px;
+ font-weight: 400;
+ line-height: 19px;
+ min-height: 40px;
+ min-width: 80px;
+ padding: 6px 20px;
+ position: relative;
+ overflow: hidden;
+ transition: background-color 0.5s;
+ background-color: #efb700;
+}
+
+.container button:hover,
+.container button:focus,
+.container button:active {
+ background-color: #fddc7b;
+}
+
+.errorText {
+ font-size: 12px;
+ text-align: center;
+ padding: 20px;
+ color: #ed6f6c;
+}
+
+@keyframes shake {
+ 0% {
+ transform: translateX(0);
+ }
+ 25% {
+ transform: translateX(2px);
+ }
+ 50% {
+ transform: translateX(0px);
+ }
+ 75% {
+ transform: translateX(-2px);
+ }
+ 100% {
+ transform: translateX(0);
+ }
+}
+
+.invalid {
+ animation: shake 0.2s ease-in-out 0s 2;
+}
+
+.noItems {
+ padding: 20px;
+ text-align: center;
+ margin-top: 20px;
+ color: #a2a3a8;
+}
+
+.clearSearch {
+ background-color: transparent !important;
+ color: #efb700 !important;
+ border-radius: 4px !important;
+ margin: 0 auto;
+ display: block;
+ text-align: center;
+ min-height: 20px !important;
+ margin-top: 20px;
+}
+
+.clearSearch:hover {
+ color: #fddc7b !important;
+}
diff --git a/examples/with-tigris/styles/LoaderWave.module.css b/examples/with-tigris/styles/LoaderWave.module.css
new file mode 100644
index 0000000000..f34a1a1b20
--- /dev/null
+++ b/examples/with-tigris/styles/LoaderWave.module.css
@@ -0,0 +1,52 @@
+.holder {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ text-align: center;
+ padding: 50px;
+ background-color: rgba(255, 255, 255, 0);
+ z-index: 1;
+ border-radius: 8px;
+}
+
+.l1 {
+ animation: load7 1s infinite ease-in-out;
+}
+.l2 {
+ animation: load7 1s infinite ease-in-out;
+ animation-delay: 0.13s;
+}
+.l3 {
+ animation: load7 1s infinite ease-in-out;
+ animation-delay: 0.35s;
+}
+
+.loader {
+ display: inline-block;
+ color: #4b4821;
+ font-size: 8px;
+ width: 14px;
+ height: 14px;
+ background-color: #9a9c9f;
+ transform: scale(0);
+ text-indent: -9999em;
+ border-radius: 50%;
+}
+
+@keyframes load7 {
+ 0% {
+ transform: scale(0);
+ }
+ 50% {
+ transform: scale(1.1);
+ }
+ 60% {
+ transform: scale(0.8);
+ }
+ 80% {
+ transform: scale(0);
+ }
+ 100% {
+ transform: scale(0);
+ }
+}
diff --git a/examples/with-tigris/styles/globals.css b/examples/with-tigris/styles/globals.css
new file mode 100644
index 0000000000..c3660a7014
--- /dev/null
+++ b/examples/with-tigris/styles/globals.css
@@ -0,0 +1,168 @@
+html,
+body,
+div,
+span,
+applet,
+object,
+iframe,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+blockquote,
+pre,
+a,
+abbr,
+acronym,
+address,
+big,
+cite,
+code,
+del,
+dfn,
+em,
+img,
+ins,
+kbd,
+q,
+s,
+samp,
+small,
+strike,
+strong,
+sub,
+sup,
+tt,
+var,
+b,
+u,
+i,
+center,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+article,
+aside,
+canvas,
+details,
+embed,
+figure,
+figcaption,
+footer,
+header,
+hgroup,
+menu,
+nav,
+output,
+ruby,
+section,
+summary,
+time,
+mark,
+audio,
+video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+ display: block;
+}
+
+html,
+body {
+ background-color: #0f2832;
+}
+
+body {
+ min-width: 768px;
+ line-height: 1;
+ font-family: 'Rubik', sans-serif;
+ color: #ffffff;
+}
+
+ol,
+ul {
+ list-style: none;
+}
+
+blockquote,
+q {
+ quotes: none;
+}
+
+button {
+ cursor: pointer;
+ border: 0;
+ font-family: inherit;
+ background-color: transparent;
+}
+
+a {
+ color: #4ed9b2;
+ text-decoration: none;
+}
+
+mark {
+ background-color: transparent;
+}
+
+input {
+ font-family: inherit;
+}
+
+h4 {
+ font-size: 26px;
+ line-height: 32px;
+ font-weight: 600;
+}
+h3 {
+ font-size: 24px;
+ line-height: 32px;
+ font-weight: 500;
+}
+h2 {
+ font-size: 20px;
+ line-height: 30px;
+ font-weight: 400;
+}
+h1 {
+ font-size: 16px;
+ line-height: 20px;
+ font-weight: 300;
+}
+
+* {
+ box-sizing: border-box;
+}
diff --git a/examples/with-tigris/tsconfig.json b/examples/with-tigris/tsconfig.json
new file mode 100644
index 0000000000..7b7e1fbbfe
--- /dev/null
+++ b/examples/with-tigris/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "es2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "commonjs",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "exclude": ["node_modules"]
+}