Home  >  Article  >  Web Front-end  >  Building a Kanban Board with Next.js,Vercel AI and Tolgee

Building a Kanban Board with Next.js,Vercel AI and Tolgee

Patricia Arquette
Patricia ArquetteOriginal
2024-11-17 22:17:02536browse

TL;DR

In this article, we will build a real-time Kanban board in Next.js using WebSockets, with database support, AI support through the Vercel AI SDK and localization via Tolgee.

What You Will Learn: ✨

  • Set up a WebSocket server in Next.js without Express.
  • Implement credentials-based authentication in Next.js with NextAuth.
  • Configure a PostgreSQL database using Docker or a cloud provider.
  • Integrate AI support for task descriptions with the Vercel AI SDK.
  • Add real-time translation and localization using Tolgee.

Star the Tolgee repository ⭐

Are you ready to build a unique Kanban board with AI and localization support? ?

Building a Kanban Board with Next.js,Vercel AI and Tolgee


Setting up the Project ?️

Initializing a Next.js Application

Initialize a new Next.js application with the following command:

ℹ️ You can use any package manager of your choice. For this project, I will use npm.

npx create-next-app@latest kanban-ai-realtime-localization --typescript --tailwind --eslint --app --src-dir --use-npm

Next, navigate into the newly created Next.js project:

cd kanban-ai-realtime-localization

Installing Dependencies

We’ll need several dependencies. Run this command to install all the dependencies required for our project:

npm install @ai-sdk/openai @tolgee/react @tolgee/web @tolgee/format-icu @tanstack/react-query @prisma/client ai socket.io socket.io-client prisma next-auth date-fns nodemon ts-node zod tsconfig-paths react-beautiful-dnd

Setting Up UI Components

For UI components, we will use shadcn/ui. Initialize it with default settings with this command:

npx shadcn@latest init -d

Now, let’s add some UI components that we are going to use in our application later on. To add reusable components from shadcn/ui, run this command:

npx shadcn@latest add button card input label select textarea toast

Inside the app/components/ui directory, some additional files will be added for these components, which we will use when building the UI for our application.


Setting up the Database Model ?

Initializing Prisma

Initialize Prisma with the following command:

npx prisma init

After you run this command, a new schema.prisma file should be created in the prisma directory at the root of your project.

Defining the Prisma Schema

Modify the newly created schema.prisma file to use PostgreSQL as the database and include the User and Task models.

// ? prisma/schema.prisma

// This is your Prisma schema file,
// learn more about it in the docs: <https://pris.ly/d/prisma-schema>

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: <https://pris.ly/cli/accelerate-init>

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id       String @id @default(cuid())
  email    String @unique
  password String

  tasks Task[] @relation("UserTasks")

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Task {
  id          String  @id @default(cuid())
  title       String
  description String?
  userId      String

  column Int
  order  Int

  createdBy User @relation("UserTasks", fields: [userId], references: [id])

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

The model is straightforward: each user can have multiple tasks, with each task linked to a specific user. A task has an integer column value representing its status (0 for ongoing, 1 for pending, and 2 for completed). The order value determines each task's position within its assigned column.

Now that we have our model ready, we need to push it to our database. For this, we need the connection URL.

If you already have access to a database with Neon or another service, that's great. Populate the .env file with the connection URL. You don't need to set up the database locally with docker.


Setting up a Database Locally with Docker ?

If you're following along and just want to try the project with a local PostgreSQL database using Docker, add a new variable named DATABASE_URL with this connection string value to the .env file.

npx create-next-app@latest kanban-ai-realtime-localization --typescript --tailwind --eslint --app --src-dir --use-npm

To run a database locally, make sure you have Docker installed. Create a new directory named scripts in the root of the project and add a file called start-local-db-docker.sh with the following lines of code:

cd kanban-ai-realtime-localization

This script basically reads the .env file for the DATABASE_URL variable and extracts all the relevant data like the username, password, the database name and creates a container if it does not exist. If it already does, it simply spins up the existing container.

Run this script to create and run a PostgreSQL container which will host all the user data for our application.

npm install @ai-sdk/openai @tolgee/react @tolgee/web @tolgee/format-icu @tanstack/react-query @prisma/client ai socket.io socket.io-client prisma next-auth date-fns nodemon ts-node zod tsconfig-paths react-beautiful-dnd

Now, we should have a running container with PostgreSQL. You can check if that is the case by running this command:

npx shadcn@latest init -d


Now, we will need a way to instantiate a Prisma client to interact with the database.

Create a new file index.ts inside the src/db directory and add the following lines of code:

npx shadcn@latest add button card input label select textarea toast

We set up a singleton instance of the PrismaClient to ensure only one instance is created and reused across your application, especially helpful in development mode.

We can now use our exported constant db to interact with our database in our application.

Run the following command to push your changes in your schema to the database.

npx prisma init

Now, to have the updated types work in the IDE, run the following command to generate new types based on our updated schema.

// ? prisma/schema.prisma

// This is your Prisma schema file,
// learn more about it in the docs: <https://pris.ly/d/prisma-schema>

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: <https://pris.ly/cli/accelerate-init>

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id       String @id @default(cuid())
  email    String @unique
  password String

  tasks Task[] @relation("UserTasks")

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Task {
  id          String  @id @default(cuid())
  title       String
  description String?
  userId      String

  column Int
  order  Int

  createdBy User @relation("UserTasks", fields: [userId], references: [id])

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

This is all that we need to set up our application database. ?


Setting up Tolgee for Localization ?️

To enable localization in your Next.js application with Tolgee, follow these steps:

  1. Create language.ts

This file handles language detection and cookie management.

// ? .env

# If you are using local DB with docker
DATABASE_URL=postgresql://postgres:password@localhost:5432/kanban-board

The setLanguage function saves the selected language (locale) as a cookie with a one-year expiry, allowing the app to remember the user's language preference across sessions.

The getLanguage function checks for the saved language in cookies. If a valid language is found, it returns that; otherwise, it attempts to detect the language from the browser's headers if running in a browser. If detection fails or the environment isn't a browser, it defaults to DEFAULT_LANGUAGE.

  1. Create shared.ts

This file contains shared constants and functions for handling localization, including fetching static data for translations

npx create-next-app@latest kanban-ai-realtime-localization --typescript --tailwind --eslint --app --src-dir --use-npm

The getStaticData function is responsible for loading translations for specific languages and namespaces to prefetch localized content. It fetches JSON files from messages directory, by language and namespace, and then bundles everything into a single object and returns it.

For language selection in our application, we will provide the user with four different language choices (English, Czech, French and German). You can add support to other languages if you like.

Inside the messages directory at the root of the project, we will store different static data for different words and sentences.

ℹ️ You can find link to these static translation file in my repository. There is nothing to explain in that file as they are bunch of translation sentences in different other languages.

The TolgeeBase function sets up Tolgee with tools for handling translations. It adds support for ICU message formatting (FormatIcu) and includes DevTools for debugging. The function uses the API key and URL from environment variables and sets English (en) as the fallback language.

  1. Update Environment Variables

We are using two different env variables, populate the .env file with these API keys. Sign up for an account in Tolgee and get access to the TOLGEE_API_KEYS, but for this application, it is not required to have that API key.

cd kanban-ai-realtime-localization

  1. Create server.ts

This file configures the Tolgee instance for server-side rendering, setting up translation handling.

npm install @ai-sdk/openai @tolgee/react @tolgee/web @tolgee/format-icu @tanstack/react-query @prisma/client ai socket.io socket.io-client prisma next-auth date-fns nodemon ts-node zod tsconfig-paths react-beautiful-dnd

This code creates a Tolgee instance for server-side translation handling. It starts by setting getLocale to use the getLanguage function, which retrieves the user’s preferred language. Then, in createTolgee, it initializes Tolgee with translation data for all supported languages through getStaticData.

It also sets Tolgee to use the provided language (from getLanguage) and configures a custom fetch function to always load fresh data by setting revalidate: 0, preventing caching of translation requests.

  1. Create client.ts

This sets up the Tolgee provider for client-side rendering.

npx shadcn@latest init -d

This code sets up a client-side Tolgee provider for translations. TolgeeProviderClient takes language, staticData, and children as props, and it initializes Tolgee with the specified language and data. Inside useEffect, it listens for language changes with permanentChange, refreshing the page through router.refresh() whenever the language updates.

Finally, TolgeeProvider renders the children, using ssr options to preload translations and displaying "Loading..." if translations aren’t ready instantly.

  1. Wrap the Application with TolgeeProviderClient in layout.tsx

Finally, wrap your application with the component to ensure all translations are accessible.

npx create-next-app@latest kanban-ai-realtime-localization --typescript --tailwind --eslint --app --src-dir --use-npm

Here first we are getting access to the locale of the user based on the header or from the cookie which we set from the function. Then we provide that locale to the tag.

That is all we need to set up Tolgee in our Next.js application. ✨This is going to be a standard process that you need to do to implement location with Tolgee in any Next.js applications.


Setting up Authentication ?️

We will be using NextAuth for authentication in our application. First, let’s start by defining a new Zod schema which we will use to validate the user-passed data.

Zod Schema for Validation

Define a Zod schema (AuthSchema) to validate user input for email and password during login and registration. This ensures the email format is correct and the password meets specified length requirements.

cd kanban-ai-realtime-localization

We require the email field to be the exact email and not any other string and we want the password field to be a minimum length of 8 characters and a max length of 20 characters. We will use this validation schema in multiple places to validate the user-passed data in our login/register form to check if it meets the criteria.

NextAuth Configuration

You set up NextAuth in route.ts under src/app/api/auth/[...nextauth], using CredentialsProvider for authentication. The authorize function validates the credentials, checks the user's existence, and verifies the password.

npm install @ai-sdk/openai @tolgee/react @tolgee/web @tolgee/format-icu @tanstack/react-query @prisma/client ai socket.io socket.io-client prisma next-auth date-fns nodemon ts-node zod tsconfig-paths react-beautiful-dnd

The authorize function logic is responsible for logging in the user or not. The function in this setup checks if the provided email and password match an existing user in the database.

We are using only the Credential based authentication. First, it validates the credentials using the AuthSchema for field validation. If validation succeeds, it looks up the user by email in the database. If the user is found, it then compares the hashed password in the database with the password input. If both checks pass, it returns the user's data (excluding the password).

As you might have guessed, here we require NEXTAUTH_SECRET variable to be defined inside the .env file. Populate the .env file with these two variables:

npx shadcn@latest init -d

User Registration API

In the src/app/api/auth/register/route.ts, we create an endpoint for user registration that hashes the password and stores user data in the database. We then return appropriate responses based on validation success.

npx create-next-app@latest kanban-ai-realtime-localization --typescript --tailwind --eslint --app --src-dir --use-npm

Here, we parse the data received from the client and validate it with the AuthSchema we wrote earlier. Then, we create a hash with a rotation value of 12. This generates an encrypted text that we will store in our database, and finally, we return the user.

Now to make our application a lot solid, let’s add a middleware that checks for the userSession anytime a user visits a certain route, and if they are not authenticated they are not allowed to visit that route.

Middleware for Route Protection

We add a middleware to restrict access to the /kanban route for unauthenticated users.

cd kanban-ai-realtime-localization

Here, we are saying that a user should not be able to visit the “/kanban” route if they are not authenticated.

We are done with the backend logic for handling authentication. Let’s work on some client-side logic.


Build Navbar Component

Our Navbar component is going to be comprised of some smaller components as well. We will have a button to login, register, logout and a select tag to allow the user to switch languages.

Let’s begin working on these components!

LangSelector Component

Inside the src/app/components directory create a new file lang-selector.tsx with the following lines of code:

npm install @ai-sdk/openai @tolgee/react @tolgee/web @tolgee/format-icu @tanstack/react-query @prisma/client ai socket.io socket.io-client prisma next-auth date-fns nodemon ts-node zod tsconfig-paths react-beautiful-dnd

The component should be pretty self-explanatory. We are using the