Trendy-day frontend frameworks like React have made state administration simpler for builders. In React, the state of a element dictates how the UI will look. Whereas managing an utility’s state has turn out to be simpler, the worldwide state idea continues to be a ache for builders.
Think about, for instance, purposes maintaining monitor of economic transactions work with a worldwide state that every one purchasers can change and observe in real-time. Such purposes are arduous to develop as a result of the builders must sync the state between purposes and maintain the ACID properties.
Convex — a worldwide state administration platform — goals to unravel this downside by offering a full-stack answer together with knowledge storage, retrieval, and mutations, all constructed into an SDK for international state administration. Its serverless method is environment friendly and makes for a extremely scalable platform. Convex is a developer-first platform with a reactive structure that aligns nicely with React, and the SDK has help for options like optimistic updates and subscriptions.
On this tutorial, we’ll construct a full-stack Subsequent.js utility with Convex for international state administration. We’ll additionally implement Convex features to question and replace the info. By the top of this tutorial, you’ll deploy the applying to Vercel. You’ll be able to comply with together with this walkthrough utilizing this GitHub repository.
Conditions
You’ll want Node.js, npm, or yarn, and a code editor like VS Code put in in your laptop. You’ll additionally want a GitHub account to make use of with Convex.
Arrange a Subsequent.js challenge
The appliance will enable customers to see a listing of current posts and submit new weblog posts. A weblog publish will comprise a title, physique, and the creator’s title.
To get began, tun the next command to arrange a brand new Subsequent.js challenge:
npx [email protected] convex-example --typescript
Open the challenge contained in the code editor and replace the pages/index.tsx
file to show a type with a view to create a weblog publish:
// pages/index.tsx import kind { NextPage } from 'subsequent' import Head from 'subsequent/head' import kinds from '../kinds/House.module.css' import { useCallback, useState } from 'react' const House: NextPage = () => { const [author, setAuthor] = useState('') const [title, setTitle] = useState('') const [body, setBody] = useState('') const createPost = async () => { // TODO: create a brand new publish inside database console.log({ creator, title, physique }) // reset the inputs after submission setAuthor('') setBody('') setTitle('') } return ( <div className={kinds.container}> <Head> <title>Subsequent.js with Convex</title> <meta title="description" content material="Generated by create subsequent app" /> <hyperlink rel="icon" href="https://weblog.logrocket.com/favicon.ico" /> </Head> <principal className={kinds.principal}> <h1 className={kinds.title}> Welcome to <a href="https://nextjs.org">Subsequent.js</a> with{' '} <a href="https://convex.dev">Convex</a> </h1> <enter kind={'textual content'} worth={title} placeholder={'Title'} className={kinds.inputStyles} onChange={(occasion) => setTitle(occasion.goal.worth)} /> <enter kind={'textual content'} worth={creator} placeholder={'Creator'} className={kinds.inputStyles} onChange={(occasion) => setAuthor(occasion.goal.worth)} /> <textarea worth={physique} rows={5} placeholder={'Submit physique '} className={kinds.inputStyles} onChange={(occasion) => setBody(occasion.goal.worth)} /> <button className={kinds.button} onClick={createPost}> Create publish </button> </principal> </div> ) } export default House
Replace the kinds/House.module.css
to the next:
/* kinds/House.module.css */ .container { padding: 0 2rem; show: flex; flex-direction: column; } .principal { padding: 4rem 0; flex: 10; show: flex; flex-direction: column; justify-content: middle; align-items: middle; } .button { font-size: 1rem; font-weight: 800; cursor: pointer; margin: 0.5rem; padding: 0.5rem; text-decoration: none; border: 1px stable #eaeaea; border-radius: 10px; transition: coloration 0.15s ease, border-color 0.15s ease; width: 200px; } .button:hover, .button:focus, .button:lively { coloration: #0070f3; border-color: #0070f3; } .inputStyles { width: 300px; margin: 10px auto; }
Run npm run dev
to begin the applying, then open http://localhost:3000/
in an online browser.
The shape for creating new posts is prepared. Now, you need to implement the logic to avoid wasting and browse the info utilizing Convex.
Arrange Convex
Convex supplies a JavaScript SDK that you need to use in your challenge.
- Run
npm i convex
to put in the Convex package deal - Contained in the challenge run
npx convex login
, this may open a web page within the browser to log in to Convex utilizing your GitHub account - After login, run
npx convex init
to initialize the Convex challenge with aconvex.json
and.env.native
for configuration. This command may even create aconvex/
listing to jot down features into
N.B., this command will immediate you for a challenge title.
Lastly, replace the pages/_app.tsx
so as to add ConvexProvider
to the whole utility. The ConvexProvider
will let you use React hooks supplied by Convex throughout the applying.
// pages/_app.tsx import '../kinds/globals.css' import kind { AppProps } from 'subsequent/app' import { ConvexProvider, ConvexReactClient } from 'convex/react' import convexConfig from '../convex.json' const convex = new ConvexReactClient(convexConfig.origin) operate MyApp({ Part, pageProps }: AppProps) { return ( <ConvexProvider shopper={convex}> <Part {...pageProps} /> </ConvexProvider> ) } export default MyApp
Add state administration utilizing Convex
With Convex arrange within the challenge, it’s time to create a knowledge mannequin and join the frontend with the database.
Outline the schema
Contained in the convex/
folder, create a brand new file schema.ts
to outline the schema for weblog posts. The defineTable
operate will create a posts
desk inside Convex.
// convex/schema.ts import { defineSchema, defineTable, s } from 'convex/schema' export default defineSchema({ posts: defineTable({ title: s.string(), creator: s.string(), physique: s.string(), }), })
Now run npx convex codegen
to generate kind definitions for the posts
schema to enhance code completions. It will let you reference posts as Doc<'posts'>
.
Extra nice articles from LogRocket:
Implement Convex features
Convex features enable the frontend to speak with the database in two methods: queries and mutation. These features are exported from information throughout the convex
listing and they’re deployed as serverless features to execute database interactions.
The frontend must learn the obtainable posts. For that, create a brand new file convex/getPosts.ts
. This file exports a question operate that returns all obtainable posts
from the database.
// convex/getPosts.ts import { question } from './_generated/server' import { Doc } from './_generated/dataModel' export default question(async ({ db }): Promise<Doc<'posts'>[]> => { return await db.desk('posts').accumulate() })
Contained in the convex/
folder, create a brand new file known as addPost.ts
. This file exports a mutation operate to permit customers so as to add a brand new publish to the database. The operate accepts a publish
object as an argument.
// convex/addPost.ts import { mutation } from './_generated/server' export default mutation( async ( { db }, publish: { creator: string; physique: string; title: string } ) => { await db.insert('posts', publish) } )
Run npx convex push
to generate the sort definitions and deploy the features to Convex.
Join the element with Convex features
Convex supplies useQuery
and useMutation
hooks to work together with the database utilizing the features carried out above.
Add the useMutation
hook to the House
element and replace the createPost
operate to name the addPost
mutation operate with the publish knowledge.
// pages/index.tsx import kind { NextPage } from 'subsequent' import Head from 'subsequent/head' import kinds from '../kinds/House.module.css' import { useCallback, useState } from 'react' import {useMutation} from "../convex/_generated/react"; const House: NextPage = () => { const addPost = useMutation('addPost') const [author, setAuthor] = useState('') const [title, setTitle] = useState('') const [body, setBody] = useState('') const createPost = async () => { await addPost({ physique, creator, title}); // reset the inputs after submission setAuthor('') setBody('') setTitle('') } return ( // return the element ) } export default House
Add the useQuery
hook to fetch and show the checklist of posts from the database. The useQuery
hook will return undefined
whereas loading knowledge, and a listing of posts afterwards.
// pages/index.tsx import kind { NextPage } from 'subsequent' import Head from 'subsequent/head' import kinds from '../kinds/House.module.css' import { useCallback, useState } from 'react' import {useMutation, useQuery} from "../convex/_generated/react"; const House: NextPage = () => { const posts = useQuery('getPosts') const addPost = useMutation('addPost') const [author, setAuthor] = useState('') const [title, setTitle] = useState('') const [body, setBody] = useState('') const createPost = async () => { await addPost({ physique, creator, title}); // reset the inputs after submission setAuthor('') setBody('') setTitle('') } return ( <div className={kinds.container}> <Head> <title>Subsequent.js with Convex</title> <meta title="description" content material="Generated by create subsequent app" /> <hyperlink rel="icon" href="https://weblog.logrocket.com/favicon.ico" /> </Head> <principal className={kinds.principal}> <h1 className={kinds.title}> Welcome to <a href="https://nextjs.org">Subsequent.js</a> with{' '} <a href="https://convex.dev">Convex</a> </h1> {posts ? ( <> <p className={kinds.description}> {'Complete posts:'} {posts.size} </p> <ul> {posts.map((publish) => ( <li key={publish._id.toString()}>{publish.title}</li> ))} </ul> </> ) : ( 'Loading posts...' )} <enter kind={'textual content'} worth={title} placeholder={'Title'} className={kinds.inputStyles} onChange={(occasion) => setTitle(occasion.goal.worth)} /> <enter kind={'textual content'} worth={creator} placeholder={'Creator'} className={kinds.inputStyles} onChange={(occasion) => setAuthor(occasion.goal.worth)} /> <textarea worth={physique} rows={5} placeholder={'Submit physique '} className={kinds.inputStyles} onChange={(occasion) => setBody(occasion.goal.worth)} /> <button className={kinds.button} onClick={createPost}> Create publish </button> </principal> </div> ) } export default House
Your utility is prepared now! Open http://localhost:3000
to see it in motion:
You’ll discover that the checklist of posts will get up to date mechanically everytime you create a brand new publish.
This habits is feasible because of the end-to-end reactivity of the Convex international state; each element utilizing the question will get up to date every time the info adjustments.
Managing Convex
Run npx convex dashboard
to log in to the Convex dashboard to handle your utility knowledge, view logs, and see the metrics for operate execution and browse/writes.
Safe the applying
Conserving the applying knowledge safe is essential, and Convex simplifies defending your knowledge utilizing identification suppliers. Convex comes with first-class help for Auth0 out of the field, and you’ll set it up very quickly.
Create an Auth0 utility
Log into your Auth0 dashboard and create a brand new Single Web page Net Software. You’ll be able to enroll for a free account on Auth0 if you happen to don’t have an account already.
Copy the Area and Shopper ID from the settings web page of this new utility and save them for later.
Within the utility settings web page, add http://localhost:3000
within the Allowed Callback URLs discipline as proven under. It will allow http://localhost:3000
to make use of Auth0 for login throughout improvement.
Arrange Auth0
Begin by operating npm i @auth0/auth0-react
to put in Auth0 in your challenge.
Then, run npx convex auth add
so as to add Auth0 as identification supplier to Convex. This command will immediate you for the Area and Shopper ID copied earlier.
Create a brand new folder known as parts/
on the root of the challenge and add a brand new file known as Login.tsx
for the Login element that has a button to immediate customers for login.
// parts/Login.tsx import { useAuth0 } from '@auth0/auth0-react' export operate Login() { const { isLoading, loginWithRedirect } = useAuth0() if (isLoading) { return <button className="btn btn-primary">Loading...</button> } return ( <principal className="py-4"> <h1 className="text-center">Convex Chat</h1> <div className="text-center"> <span> <button className="btn btn-primary" onClick={loginWithRedirect}> Log in </button> </span> </div> </principal> ) }
Replace the pages/_app.tsx
to interchange the ConvexProvider
with ConvexProviderWithAuth0
.
import '../kinds/globals.css' import { ConvexProviderWithAuth0 } from 'convex/react-auth0' import { ConvexReactClient } from 'convex/react' import convexConfig from '../convex.json' import { AppProps } from 'subsequent/app' import { Login } from '../parts/Login' const convex = new ConvexReactClient(convexConfig.origin) const authInfo = convexConfig.authInfo[0] operate MyApp({ Part, pageProps }: AppProps) { return ( <ConvexProviderWithAuth0 shopper={convex} authInfo={authInfo} loggedOut={<Login />} > <Part {...pageProps} /> </ConvexProviderWithAuth0> ) } export default MyApp
Now, while you open the applying http://localhost:3000
, you’ll see a login button as an alternative of the publish type.
Combine Auth0 with Convex
Now, that you’ve got Auth0 configured, you possibly can safe the mutation operate. The mutation
operate supplies the authentication data because the auth
object. The addPost
mutation will now reject any unauthenticated requests.
// convex/addPost.ts import { mutation } from './_generated/server' export default mutation( async ( { db, auth }, publish: { creator: string; physique: string; title: string } ) => { const identification = await auth.getUserIdentity() if (!identification) { throw new Error('Referred to as addPosts with out authentication current!') } await db.insert('posts', publish) } )
You can even replace the code on the frontend to make use of the logged-in person’s title because the creator discipline.
// pages/index.tsx import kind { NextPage } from 'subsequent' import Head from 'subsequent/head' import kinds from '../kinds/House.module.css' import { useCallback, useState } from 'react' import {useMutation, useQuery} from "../convex/_generated/react"; import {useAuth0} from "@auth0/auth0-react"; const House: NextPage = () => { const {person} = useAuth0() const posts = useQuery('getPosts') const addPost = useMutation('addPost') const [title, setTitle] = useState('') const [body, setBody] = useState('') const createPost = async () => { if(person?.title) { await addPost({ physique, creator: person.title, title}); } // reset the inputs after submission setBody('') setTitle('') } return ( <div className={kinds.container}> <Head> <title>Subsequent.js with Convex</title> <meta title="description" content material="Generated by create subsequent app" /> <hyperlink rel="icon" href="https://weblog.logrocket.com/favicon.ico" /> </Head> <principal className={kinds.principal}> <h1 className={kinds.title}> Welcome to <a href="https://nextjs.org">Subsequent.js</a> with{' '} <a href="https://convex.dev">Convex</a> </h1> {posts ? ( <> <p className={kinds.description}> {'Complete posts:'} {posts.size} </p> <ul> {posts.map((publish) => ( <li key={publish._id.toString()}>{publish.title}</li> ))} </ul> </> ) : ( 'Loading posts...' )} <enter kind={'textual content'} worth={title} placeholder={'Title'} className={kinds.inputStyles} onChange={(occasion) => setTitle(occasion.goal.worth)} /> <textarea worth={physique} rows={5} placeholder={'Submit physique '} className={kinds.inputStyles} onChange={(occasion) => setBody(occasion.goal.worth)} /> <button className={kinds.button} onClick={createPost}> Create publish </button> </principal> </div> ) } export default House
Deploy to Vercel
To deploy the applying, push your code (together with convex.json
) to a repository on GitHub and hyperlink it to your Vercel account:
Change the construct command with npx convex push && subsequent construct
to push the newest features to Convex whereas deploying, and add the CONVEX_ADMIN_KEY
atmosphere variable from the .env.native
:
As soon as the applying is deployed, copy the deployment URL (.vercel.app
):
Add the URL to the Allowed Callback URLs checklist alongside the http://localhost:3000
within the Auth0 utility settings.
Conclusion
Your utility is now deployed on Vercel. On this tutorial, we discovered about international state administration and the way to deploy a Subsequent.js utility with state administration utilizing Convex. We additionally discovered about securing the applying with Auth0 and deploying it to Vercel. You’ll be able to lengthen the above utility to make use of superior options like optimistic updates and indexes to make it even sooner.
You’ll be able to strive Convex without cost as we speak and learn extra of their documentation.