modernc.org/sqlite with Go
Explore how to use modernc.org/sqlite with Go to handle SQLite's single-writer, multiple-reader model, optimize PRAGMA settings, and avoid CGo.
Have you ever had trouble choosing the right tools for your full-stack web application? Well, you are not alone. In this blog post, we will show you how to build a full-stack web application with minimum configuration and using only one language, TypeScript. We will create a simple CRUD app using Next.JS for frontend/backend and Prisma ORM with PostgreSQL for database operations.
Prisma
It is a next-generation ORM for Node.JS with TypeScript support out of the box. It has large feature sets such as:
Next.JS
It is a React framework for building user interfaces. Nowadays, Next is one of the most popular frontend frameworks in the market due to its wide variety of tooling options including:
To start the development, we need to run a few important commands. First, we will set up a basic Next.JS app with Typescript, install the Prisma CLI and finally initialize a basic Prisma setup. If you like to skip these steps, please check the results in the repository. Please check the sources section at the end of the article for the link.
npx create-next-app@latest –typescript npm install prisma –save-dev npx prisma init
The npx prisma init command created two files:
The prisma.schema is the main configuration file for our Prisma project. It consists of three main parts:
Since we are building a small CRUD application to handle personal goals per user, the next step is to create the User and Goal models for it.
generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id Int @id @default(autoincrement()) email String @unique firstName String @map("first_name") lastName String @map("last_name") goals Goal[] @@map("user") // table name in the database } model Goal { id Int @id @default(autoincrement()) name String description String @db.VarChar(255) priority Priority user User? @relation(fields: [userId], references: [id]) userId Int? @map("user_id") @@map("goal") // table name in the database } enum Priority { LOW MEDIUM HIGH }
For further details about Prisma models, please, check the official Prisma site, where you will find detailed documentation about data modelling.
The only thing missing is to run the following command:
npx prisma migrate dev
This will generate a migration file and update our database.
Our tables have been created. Now we have to fill them with actual data. The Next.JS built-in API routing system will help with that because, with it, we can easily write code that’s running on the server. The only thing remaining is to use the query builders. For that, we need to install and instantiate a Prisma Client with the following command and code snippet:
npm install @prisma/client
import { PrismaClient } from '@prisma/client' export const prisma = new PrismaClient()
Important note: The name of the PrismaClient is a bit misleading since we cannot use it on the client side. It must run on the server. Please avoid calling it directly inside lifecycle methods or side effects.
Let’s create the API endpoints on the server by creating the following files under the api directory and use the prisma provided query builders for CRUD operations inside them. If you don’t want to add an extra API layer, it will be fine to use the query builders with server-side rendering (SSR) and static-site generation (SSG).
const { name, description, priority } = req.body const user = await prisma.user.findFirst() const result = await prisma.goal.create({ data: { name, description, priority, user: { connect: { id: user?.id } } } })
const goals = await prisma.goal.findMany()
const goalId = parseInt(req.query.id as string) const goal = await prisma.goal.findUnique({ where: { id: goalId } })
const goalId = parseInt(req.query.id as string) const { name, description, priority } = req.body const goal = await prisma.goal.update({ where: { id: goalId }, data: { name, description, priority } })
const goalId = parseInt(req.query.id as string) await prisma.goal.delete({ where: { id: goalId } })
Note: Please check the repository for the full code
Last, we have to call these endpoints on the pages. We will use server-side rendering (SSR) for fetching goals and client-side logic for deleting one, but the choice is up to you since Next.JS supports various rendering options. Create a goals.tsx file under the pages folder and write the followings there:
interface GoalsProps { goals: Goal[] } const Goals: NextPage<GoalsProps> = ({ goals }) => { const router = useRouter() const handleDelete = useCallback(async (goalId: number) => { try { const res = await fetch(`/api/goals/${goalId}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' } }) if (res.ok) { router.replace(router.asPath) } } catch (e) { console.error(e) } }, []) return ( <LayoutBase> <Grid container direction="column" spacing={2} sx={{ my: 4 }}> {goals.map((goal, index) => ( <GoalItem goal={goal} key={index} onDelete={handleDelete} /> ))} </Grid> </LayoutBase> ) } export const getServerSideProps: GetServerSideProps = async () => { const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/goals`) const data = await response.json() return { props: { goals: data } } }
Note: Check the repository for the other pages and components.
Now we have a fully functioning full-stack Next.JS application using Prisma for database operations. Next.JS & Prisma stack is a perfect option if you need a relatively small full-stack application without any complex logic, with minimum configuration, and using the same language for the server and client. However, if you need a more complex application, you should look for a better and more battle-hardened alternative.
Explore how to use modernc.org/sqlite with Go to handle SQLite's single-writer, multiple-reader model, optimize PRAGMA settings, and avoid CGo.
By understanding the nuances of signal handling in containerized environments, we can prevent unexpected disruptions and maintain robust application performance.
React has become a dominant force in the world of web development, enabling developers to create highly interactive and dynamic user interfaces.