Medium Clone with NextJs, Convex, and Clerk
Are you ready to build a full-featured, real-time blogging platform similar to Medium? Look no further! In this blog post, we'll dive into an exciting video tutorial that walks you through the process of creating a Medium clone using cutting-edge technologies and techniques.
Source code
Enter your Github username and email to access the source code.
What You'll Learn
Our tutorial covers the creation of a modern blogging platform with real-time capabilities. You'll learn how to:
- Set up a Next.js project for a blogging platform
- Implement secure user authentication with Clerk
- Create a real-time database and file storage system using Convex
- Build a full-featured article publishing and reading experience
- Implement real-time likes and updates across all users
Tech Stack Overview
The tutorial leverages a powerful combination of technologies:
- NextJs: A React framework for building server-side rendered and static web applications
- Convex: A backend platform providing real-time database and file storage capabilities
- Clerk: A complete user management and authentication solution
Key Features of the Medium Clone
- User Authentication: Secure sign-up and login functionality using Clerk
- Article Creation and Publishing: Allow users to write and publish articles
- Real-Time Updates: Instant updates for likes and new articles across all users
- File Storage: Handle image uploads for article content
- User Data Synchronization: Use Clerk webhooks to sync user data with Convex backend
Let's dive into some code examples to see how these features are implemented.
Deep Dive: Integrating Convex and Clerk
1. Setting up Convex Schema
First, let's define our Convex schema for articles:
// convex/schema.ts
import { defineSchema, defineTable } from 'convex/server'
import { v } from 'convex/values'
export default defineSchema({
users: defineTable({
email: v.string(),
clerkUserId: v.string(),
firstName: v.optional(v.string()),
lastName: v.optional(v.string()),
imageUrl: v.optional(v.string()),
posts: v.optional(v.array(v.id('posts')))
}).index('byClerkUserId', ['clerkUserId']),
posts: defineTable({
title: v.string(),
slug: v.string(),
excerpt: v.string(),
content: v.string(),
coverImageId: v.optional(v.id('_storage')),
authorId: v.id('users'),
likes: v.number()
}).index('bySlug', ['slug'])
})
2. Implementing Article Creation
Here's how we might implement article creation using Convex:
// convex/articles.ts
import { v } from 'convex/values'
import { query, mutation } from './_generated/server'
import { getCurrentUserOrThrow } from './users'
export const createPost = mutation({
args: {
title: v.string(),
slug: v.string(),
excerpt: v.string(),
content: v.string(),
coverImageId: v.optional(v.id('_storage'))
},
handler: async (ctx, args) => {
const user = await getCurrentUserOrThrow(ctx)
const data = {
...args,
authorId: user._id,
likes: 0
}
await ctx.db.insert('posts', data)
return data.slug
}
})
3. Real-Time Article Fetching
To fetch articles in real-time, we can use Convex queries:
// convex/articles.ts
export const getPosts = query({
args: {},
handler: async ctx => {
const posts = await ctx.db.query('posts').order('desc').collect()
return Promise.all(
posts.map(async post => {
const author = await ctx.db.get(post.authorId)
return {
...post,
author,
...(post.coverImageId
? {
coverImageUrl:
(await ctx.storage.getUrl(post.coverImageId)) ?? ''
}
: {})
}
})
)
}
})
4. Implementing Real-Time Likes
Here's how we can implement real-time likes using Convex mutations:
// convex/articles.ts
import { mutation } from './_generated/server'
export const likePost = mutation({
args: { slug: v.string() },
handler: async (ctx, { slug }) => {
const user = await getCurrentUserOrThrow(ctx)
const post = await ctx.db
.query('posts')
.withIndex('bySlug', q => q.eq('slug', slug))
.unique()
if (!post) {
return null
}
if (post.authorId === user._id) {
return null
}
await ctx.db.patch(post._id, { likes: post.likes + 1 })
}
})
Syncing Clerk User Data with Convex
To keep user data in sync between Clerk and Convex, we'll use Convex's HTTP endpoints to create a POST endpoint that receives Clerk webhooks. This approach allows us to handle user creation, updates, and deletions directly within our Convex backend.
Here's how we implement this using Convex's http.route
:
// convex/http.ts
import { httpRouter } from 'convex/server'
import { internal } from './_generated/api'
import { httpAction } from './_generated/server'
const http = httpRouter()
http.route({
path: '/clerk-users-webhook',
method: 'POST',
handler: httpAction(async (ctx, request) => {
const event = await validateRequest(request)
if (!event) {
return new Response('Error occurred', { status: 400 })
}
switch (event.type) {
case 'user.created': // intentional fallthrough
case 'user.updated':
await ctx.runMutation(internal.users.upsertFromClerk, {
data: event.data
})
break
case 'user.deleted': {
const clerkUserId = event.data.id!
await ctx.runMutation(internal.users.deleteFromClerk, {
clerkUserId
})
break
}
default:
console.log('Ignored Clerk webhook event', event.type)
}
return new Response(null, { status: 200 })
})
})
export default http
In this implementation:
- We create a POST route at
/clerk-users-webhook
to receive Clerk webhook events. - The
validateRequest
function (not shown) would verify the webhook's authenticity. - We handle three types of events:
user.created
anduser.updated
: These events trigger theupsertFromClerk
mutation to create or update user data in Convex.user.deleted
: This event triggers thedeleteFromClerk
mutation to remove the user from Convex.
- Any other event types are logged but not acted upon.
This approach allows for real-time synchronization of user data between Clerk and Convex, ensuring that your application always has the most up-to-date user information available for use in queries and mutations.
Conclusion
This video tutorial offers a comprehensive guide to building a modern, real-time blogging platform similar to Medium. The combination of Next.js, Convex, and Clerk creates a powerful system that allows for real-time updates, secure authentication, and seamless data management.
Whether you're building a personal blog or a large-scale publishing platform, the techniques and technologies covered in this tutorial will provide you with the tools you need to create a feature-rich, real-time, and user-friendly blogging experience.
Don't miss out on this opportunity to level up your full-stack development skills and create an impressive Medium clone. Watch the tutorial now and start building your next-generation blogging platform today!