Hi Everyone! Creator X here 👋
As I promised last week, I'm diving into my latest project and sharing the journey with you all. First, I want to give a massive shout-out to Artem Lazarev - he's also on YouTube and was a real lifesaver for this project. Definitely check out his template; it's absolutely amazing!
My Tech Stack
Here's what I'm working with for this build:
- Next.js - React framework
- Shadcn/Tailwind - UI components and styling
- Convex - Database and backend
- Clerk Auth - Authentication
- TypeScript - Type safety
- Python - (not fully implemented yet)
- Vercel - Deployment
Quick Check-in
How are you doing? What are you looking to achieve this week? Any new projects you're working on? Let me know in the comments!
Setting Up Next.js
Let's start with the basics. First, create your Next.js app:
npx create-next-app@latest my-app
You'll get prompted with several questions:
- What is your project named?
my-app
- Would you like to use TypeScript?
Yes
- Which linter would you like to use?
ESLint
- Would you like to use Tailwind CSS?
Yes
- Would you like your code inside a
src/
directory?Yes
- Would you like to use App Router? (recommended)
Yes
- Would you like to use Turbopack? (recommended)
Yes
- Would you like to customize the import alias?
No
Setting Up Convex
Next, install the Convex library:
npm install convex
Then run:
npx convex dev
This sets up your environment and will ask you to login or work as a guest. Just follow the instructions to confirm your account. You can either import an existing project from the Convex dashboard or start fresh. Once done, it creates a convex/
folder with a _generated/
folder inside.
My Journey from Traditional Databases to Convex
Moving from traditional databases to Convex was definitely something different. At first, it's confusing, but once you start understanding the basics, it clicks.
Creating Your Schema
First, I created my schema.ts
:
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
users: defineTable({
// Your user fields here
}),
// Add more tables as needed
});
Pro tip: You can also use the import command if you have existing data:
npx convex import --table tasks sampleData.jsonl
I only learned this later! 😅
Understanding Mutations (My "Aha!" Moment)
This is where I got confused initially. Convex uses something called mutations, and here's how I understand them:
Mutations are functions that handle your database operations - creating, updating, and retrieving data. They work as a bridge between your frontend and database.
Think of it this way:
- Your
.tsx
page has a form (frontend) - Your mutation takes that form data and saves it to the database
- The same mutation can pull data back to display it
They usually work in pairs. If you have business_profile.tsx
, you'd have businessProfile.ts
for the mutations.
Complete Mutation Example
Here's a simple, complete example of how mutations work:
Backend - Mutation file (convex/tasks.ts
):
import { v } from "convex/values";
import { query, mutation } from "./_generated/server";
// Create a new task
export const createTask = mutation({
args: {
title: v.string(),
description: v.string(),
},
handler: async (ctx, args) => {
const taskId = await ctx.db.insert("tasks", {
title: args.title,
description: args.description,
completed: false,
createdAt: Date.now(),
});
return taskId;
},
});
// Get all tasks
export const getAllTasks = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query("tasks").collect();
},
});
Frontend - React Component (app/tasks/page.tsx
):
"use client";
import React, { useState } from 'react';
import { useMutation, useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
export default function TasksPage() {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const createTask = useMutation(api.tasks.createTask);
const tasks = useQuery(api.tasks.getAllTasks);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await createTask({ title, description });
setTitle("");
setDescription("");
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Task title"
required
/>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Task description"
/>
<button type="submit">Add Task</button>
</form>
<div>
{tasks?.map((task) => (
<div key={task._id}>
<h3>{task.title}</h3>
<p>{task.description}</p>
</div>
))}
</div>
</div>
);
}
Schema (convex/schema.ts
):
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
tasks: defineTable({
title: v.string(),
description: v.string(),
completed: v.boolean(),
createdAt: v.number(),
}),
});
Important: All mutations must go in your convex/
folder so they get added to _generated/
.
Setting Up the Client Provider
Create ConvexClientProvider.tsx
in your app/
folder. This allows your app to communicate with Convex:
// This wraps ConvexProvider and passes ConvexReactClient
Then wrap your app/layout.tsx
children with <ConvexClientProvider>
.
Displaying Data
To show your data on the frontend:
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
Common Issues I Encountered (Save Yourself Some Pain!)
Run
npx convex dev
strategically - Don't run it after every single file creation. Create a few pages first, then run it. You'll get fewer errors and they're more manageable._generated folder not updating? - Sometimes after running
npx convex dev
, the_generated/
folder doesn't update properly. If this happens:
rm -r _generated/
npx convex dev
It's safe to delete - Convex will recreate it.
- Check the dashboard logs - If you get TypeScript errors that don't make sense, check your Convex dashboard logs. They often have more detailed error information.
- Test your functions - Cool feature: you can test functions directly in the Convex dashboard! Select a function and click "Run function" on the right.
Environment Variables and Deployment
In your Convex settings, you'll find:
- Deployment URL
- HTTP Actions URL
Copy these to your Vercel environment variables so they can communicate properly. You can also add other environment variables that work with your Convex database there.
Final Thoughts
To be honest, I used to hate databases with a passion. I've tried several and failed miserably. The only two I actually enjoy working with are PostgreSQL and now Convex. What I love about Convex is how straightforward it becomes once you understand the basics.
My advice: take your time and enjoy the process. The learning curve is worth it!
What's Next?
In my next post, I'll dive into Clerk authentication and deployment.
The project I'm building is called GritPath OS - a platform that helps businesses create blueprints to level up. I'm thrilled to say I finally got it on Product Hunt! 🎉
That's part one of my dev journal, with plenty more to come.
Note: These guides are aimed at helping beginners. If you have more experience with Convex, please drop your insights in the comments!
Thanks for reading, and don't forget to say hello! 👋
What challenges have you faced when switching to new databases? Let me know in the comments!
Top comments (0)