Our great sponsors
- Appwrite - The open-source backend cloud platform
- InfluxDB - Collect and Analyze Billions of Data Points in Real Time
- Revelo Payroll - Free Global Payroll designed for tech teams
- Onboard AI - Learn any GitHub repo in 59 seconds
- Sonar - Write Clean JavaScript Code. Always.
-
Have Docker Desktop installed and running
-
For styling, we will use TailwindCSS. And to handle calling AppWrite from the front-end, we will use the data fetching and caching library React Query.
-
Appwrite
Appwrite - The open-source backend cloud platform. The open-source backend cloud platform for developing Web, Mobile, and Flutter applications. You can set up your backend faster with real-time APIs for authentication, databases, file storage, cloud functions, and much more!
-
import { useState } from 'react'; const messages = [ { $id: 1, message: 'Hello world' }, { $id: 2, message: 'Hello world 2' }, ]; export default function Home() { const [input, setInput] = useState(''); return ( Message board {messages?.map((message) => ( {message.message} Delete ))} setInput(e.target.value)} className="w-full border border-gray-300 rounded-md p-2 mt-4" /> Submit message ); }
Enter fullscreen mode Exit fullscreen modeBy the way, if you want to see how the application looks, start it with by typing
npm run dev
in your terminal in themessage-board-app/
directory.Adding new messages
First, create a new file at
src/appwrite.js
which will contain all our communication with the AppWrite backend. TheaddMessage
is an asynchronous function. It takes the message string as an input. Then it will make a call to our AppWrite instance where the message will be saved to the database.
import { Account, Client, Databases, ID } from 'appwrite'; const client = new Client(); const account = new Account(client); const database = process.env.NEXT_PUBLIC_DATABASE; const collection = process.env.NEXT_PUBLIC_MESSAGES_COLLECTION; client.setEndpoint(process.env.NEXT_PUBLIC_ENDPOINT).setProject(process.env.NEXT_PUBLIC_PROJECT); const databases = new Databases(client); export const addMessage = async (message) => { await databases.createDocument( database, collection, ID.unique(), { message, } ); };
Enter fullscreen mode Exit fullscreen modeThen modify the
src/pages/index.js
component to hook up the form for adding messages:
import { useState } from 'react'; import { useMutation, useQueryClient } from 'react-query'; import { addMessage } from '@/appwrite'; const messages = [ { $id: 1, message: 'Hello world' }, { $id: 2, message: 'Hello world 2' }, ]; export default function Home() { const [input, setInput] = useState(''); const queryClient = useQueryClient(); const addMessageMutation = useMutation(addMessage, { onSuccess: () => { setInput(''); queryClient.invalidateQueries('messages'); }, }); return (
Message board
-
{messages?.map((message) => (
-
{message.message}
Delete
))}
Enter fullscreen mode Exit fullscreen modeNow we can add messages to the database. However, we can’t see them yet as we are not fetching any messages from AppWrite. We will fix that shortly.
But what exactly are we doing here? We have created a React Query mutation with the
useMutation
hook. It allows us to then calladdMessageMutation.mutate(input)
on the “Submit message” button. And even more importantly, in the hook, we added anonSucess
callback. So whenever adding a new message is successful, we can clear the input field and invalidate the query cache. Invalidating the cache will prompt React Query to fetch all messages again. This will become useful in the next section where we fetch messages from AppWrite.Rendering messages
Now that we can add messages to the database, we also want to display them. First we’ll need to add the fetch messages action to the
src/appwrite.js
file:
// other code export const getMessages = async () => { const { documents: messages } = await databases.listDocuments(database, collection); return messages; };
Enter fullscreen mode Exit fullscreen modeNow we can fetch them with the React Query
useQuery
hook:
import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import { addMessage, getMessages } from '@/appwrite'; export default function Home() { const [input, setInput] = useState(''); const queryClient = useQueryClient(); const { data: messages } = useQuery('messages', getMessages); const addMessageMutation = useMutation(addMessage, { onSuccess: () => { setInput(''); queryClient.invalidateQueries('messages'); }, }); return (
Message board
-
{messages?.map((message) => (
-
{message.message}
Delete
))}
Enter fullscreen mode Exit fullscreen modeAdd some messages via the form so you can see them! Notice that whenever you submit a new message, the existing messages get automatically reloaded - so that the new message appears in the list. That’s the magic of React Query.
Deleting messages
The last missing step is the ability to delete messages. Once again, we first need to add this function to
src/appwrite.js
:
// other code export const deleteMessage = async (id) => { await databases.deleteDocument(database, collection, id); };
Enter fullscreen mode Exit fullscreen modeAnd then, all that’s left is adding one more mutation and the
onClick
action on the delete button to thesrc/pages/index.js
page. Don’t copy the whole component. Only copy the added delete mutation and the new return statement. Also, make sure to importdeleteMessage
from AppWrite.
import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import { addMessage, deleteMessage, getMessages } from '@/appwrite'; export default function Home() { // other code const deleteMessageMutation = useMutation(deleteMessage, { onSuccess: () => { queryClient.invalidateQueries('messages'); }, }); return (
Message board
-
{messages?.map((message) => (
-
{message.message}
deleteMessageMutation.mutate(message.$id)} className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md" > Delete
))}
Enter fullscreen mode Exit fullscreen modeOnce again, after successfully deleting a message we tell React Query to refetch all messages so that the deleted message disappears from the list.
Authentication
To add authentication, we first need some additional actions in the
src/appwrite.js
file. This is so that users can sign up and afterward sign in. Also, we need a sign out action and a function to fetch the user session. To store the user session, also create a new React Context in this file. Make sure to importcreateContext
fromreact
.
// other code export const signUp = async (email, password) => { return await account.create(ID.unique(), email, password); }; export const signIn = async (email, password) => { return await account.createEmailSession(email, password); }; export const signOut = async () => { return await account.deleteSessions(); }; export const getUser = async () => { try { return await account.get(); } catch { return undefined; } }; // import createContext from react beforehand export const UserContext = createContext(null);
Enter fullscreen mode Exit fullscreen modeWe now need to fetch the user automatically on every application start and make it available through our context. We’ll do this in
src/pages/_app.js
:
import { useEffect, useState } from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { UserContext, getUser } from '@/appwrite'; import '@/styles/globals.css'; export default function App({ Component, pageProps }) { const [user, setUser] = useState(null); const queryClient = new QueryClient(); useEffect(() => { const user = async () => { const user = await getUser(); if (!user) return; setUser(user); }; user(); }, []); return ( ); }
Enter fullscreen mode Exit fullscreen modeThen we can build the masks for sign up. For this add a new file
src/pages/signup.js
with the following content:
import { useRouter } from 'next/router'; import { useState } from 'react'; import { signUp } from '@/appwrite'; export default function SignUp() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const router = useRouter(); const handleSubmit = async (e) => { e.preventDefault(); try { await signUp(email, password); router.push('/'); } catch { console.log('Error signing up'); } }; return (
Email setEmail(e.target.value)} />Password setPassword(e.target.value)} />{/* Submit button */}Sign UpEnter fullscreen mode Exit fullscreen modeAnd sign in at
src/pages/signin.js
:
import { useRouter } from 'next/router'; import { useState } from 'react'; import { signIn } from '@/appwrite'; export default function SignIn() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const router = useRouter(); const handleSubmit = async (e) => { e.preventDefault(); try { await signIn(email, password); router.push('/'); } catch { console.log('Error signing in'); } }; return (
Email setEmail(e.target.value)} />Password setPassword(e.target.value)} />Sign InEnter fullscreen mode Exit fullscreen modeSigning out
Now, for the last piece of the user authentication, we need to hook up the sign out functionality. We already have all the pieces for this. We just need to check if the
user
session exists and if yes, display a “Sign Out” button that calls thesignOut
function from AppWrite. We’ll change thesrc/pages/index.js
file to this:
import { useContext, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import { addMessage, getMessages, deleteMessage, UserContext, signOut } from '@/appwrite'; export default function Home() { const [input, setInput] = useState(''); const queryClient = useQueryClient(); const { user, setUser } = useContext(UserContext); const { data: messages } = useQuery('messages', getMessages); const addMessageMutation = useMutation(addMessage, { onSuccess: () => { setInput(''); queryClient.invalidateQueries('messages'); }, }); const deleteMessageMutation = useMutation(deleteMessage, { onSuccess: () => { queryClient.invalidateQueries('messages'); }, }); const handleSignOut = async () => { await signOut(); setUser(null); }; return (
Message board
-
{messages?.map((message) => (
-
{message.message}
deleteMessageMutation.mutate(message.$id)} className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md" > Delete
))}
Enter fullscreen mode Exit fullscreen modeAccess control
With authentication implemented, we now want to ensure that only authenticated users can create new messages. And that users can only delete their own messages. So let’s go back to the dashboard and change the
messages
schema permissions to this:As you can see, unauthenticated users can only read messages from now on. If you want to create messages, you need to be authenticated. But how can we enable users to delete their own messages? We’ll do this on a per-document basis. Think of a document as one row in the database. So for that, on the same screen, also make sure you turn on “Document Security”.
Back in the code, modify the
addMessage
function insrc/appwrite.js
as follows. Also, don’t forget to update the import statement as shown.
import { Account, Client, Databases, Permission, Role, ID } from 'appwrite'; export const addMessage = async ({ message, userId }) => { await databases.createDocument( database, collection, ID.unique(), { message, }, [Permission.delete(Role.user(userId))] ); };
Enter fullscreen mode Exit fullscreen modeThat way, we explicitly state that only the owner of the document can delete it. Now, in
src/app/index.js
in the add message button action, also pass theuser.$id
of our current user, so AppWrite knows which user we want to give delete permission to:
addMessageMutation.mutate({ message: input, userId: user?.$id })} className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md mt-4" > Submit message
Enter fullscreen mode Exit fullscreen modeNow, create a new user on
/signup
and then use the same credentials on/signin
to sign in. Afterward, you will be able to create new messages linked to your account. Try deleting those messages and see what happens.Showing the auth state in the UI
Great! Now only owners of messages can delete them. But we’re still showing a delete button beside every message. No matter which user we’re logged in with. Same for the submit a new message field which is visible for unauthenticated users. So we need to change our UI only to show these options when they can actually be performed by the user. For the submit a new message form that’s simple. We’ll just show it whenever a user is logged in:
{user && ( <> setInput(e.target.value)} className="w-full border border-gray-300 rounded-md p-2 mt-4" /> addMessageMutation.mutate({ message: input, userId: user?.$id })} className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md mt-4" > Submit message Sign out > )}
Enter fullscreen mode Exit fullscreen modeIt’s a bit more complicated for checking if a user owns the message. Every message comes with a permissions array. If the user owns that message, their
user.$id
will be present in thedelete
permission field. We’ll write a function to check for that:
const canDelete = (userID, array) => { return array.some((element) => element.includes('delete') && element.includes(userID)); };
Enter fullscreen mode Exit fullscreen modeAnd then check for that in the UI for each message to see if we need to render the delete button:
{messages?.map((message) => (
{message.message}
{canDelete(user?.$id, message.$permissions) && ( deleteMessageMutation.mutate(message.$id, user.$id)} className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md" > Delete )}Enter fullscreen mode Exit fullscreen modeWrapping up
And with this, we’re done! Users will now only see the actions they actually have permission for. As this tutorial showed, Next.js, AppWrite, and React Query played very nicely together. Thanks to AppWrite in combination with React Query we get an amazing full-stack development experience without writing any backend code.
We hope you learned something new while following this tutorial. The application we built can serve as a starting point for your own project. If you want to continue building this message board, here are a few feature ideas:
- Add comments to messages. This will teach you how to do relations in AppWrite.
- Add infinite scrolling or pagination to deal with a large number of messages that cannot be rendered all at once.
- Use Next’s Server Side Rendering to fetch and render messages on the server instead of the client.
Regarding deployment, we already set up the AppWrite in a neatly packaged docker-compose setup. You could host this on popular Cloud Providers such as Google Cloud or AWS. As a bonus, add Next.js into the Docker environment using this guide by Vercel.
And while you’re at it, you can also set up preview environments for your project using Preevy. This will easily provision preview environments for your application that you can share with others to get quick feedback and keep your development workflow moving at a good clip.
If you don’t want to go down the self-hosting route, AppWrite also offers a cloud service where that host it for you. But then you’ll need to host the Next.js separately on Vercel or Netlify.
Hope you enjoyed and found this guide helpful.
Good luck!
-
-
import { useState } from 'react'; const messages = [ { $id: 1, message: 'Hello world' }, { $id: 2, message: 'Hello world 2' }, ]; export default function Home() { const [input, setInput] = useState(''); return ( Message board {messages?.map((message) => ( {message.message} Delete ))} setInput(e.target.value)} className="w-full border border-gray-300 rounded-md p-2 mt-4" /> Submit message ); }
Enter fullscreen mode Exit fullscreen modeBy the way, if you want to see how the application looks, start it with by typing
npm run dev
in your terminal in themessage-board-app/
directory.Adding new messages
First, create a new file at
src/appwrite.js
which will contain all our communication with the AppWrite backend. TheaddMessage
is an asynchronous function. It takes the message string as an input. Then it will make a call to our AppWrite instance where the message will be saved to the database.
import { Account, Client, Databases, ID } from 'appwrite'; const client = new Client(); const account = new Account(client); const database = process.env.NEXT_PUBLIC_DATABASE; const collection = process.env.NEXT_PUBLIC_MESSAGES_COLLECTION; client.setEndpoint(process.env.NEXT_PUBLIC_ENDPOINT).setProject(process.env.NEXT_PUBLIC_PROJECT); const databases = new Databases(client); export const addMessage = async (message) => { await databases.createDocument( database, collection, ID.unique(), { message, } ); };
Enter fullscreen mode Exit fullscreen modeThen modify the
src/pages/index.js
component to hook up the form for adding messages:
import { useState } from 'react'; import { useMutation, useQueryClient } from 'react-query'; import { addMessage } from '@/appwrite'; const messages = [ { $id: 1, message: 'Hello world' }, { $id: 2, message: 'Hello world 2' }, ]; export default function Home() { const [input, setInput] = useState(''); const queryClient = useQueryClient(); const addMessageMutation = useMutation(addMessage, { onSuccess: () => { setInput(''); queryClient.invalidateQueries('messages'); }, }); return (
Message board
-
{messages?.map((message) => (
-
{message.message}
Delete
))}
Enter fullscreen mode Exit fullscreen modeNow we can add messages to the database. However, we can’t see them yet as we are not fetching any messages from AppWrite. We will fix that shortly.
But what exactly are we doing here? We have created a React Query mutation with the
useMutation
hook. It allows us to then calladdMessageMutation.mutate(input)
on the “Submit message” button. And even more importantly, in the hook, we added anonSucess
callback. So whenever adding a new message is successful, we can clear the input field and invalidate the query cache. Invalidating the cache will prompt React Query to fetch all messages again. This will become useful in the next section where we fetch messages from AppWrite.Rendering messages
Now that we can add messages to the database, we also want to display them. First we’ll need to add the fetch messages action to the
src/appwrite.js
file:
// other code export const getMessages = async () => { const { documents: messages } = await databases.listDocuments(database, collection); return messages; };
Enter fullscreen mode Exit fullscreen modeNow we can fetch them with the React Query
useQuery
hook:
import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import { addMessage, getMessages } from '@/appwrite'; export default function Home() { const [input, setInput] = useState(''); const queryClient = useQueryClient(); const { data: messages } = useQuery('messages', getMessages); const addMessageMutation = useMutation(addMessage, { onSuccess: () => { setInput(''); queryClient.invalidateQueries('messages'); }, }); return (
Message board
-
{messages?.map((message) => (
-
{message.message}
Delete
))}
Enter fullscreen mode Exit fullscreen modeAdd some messages via the form so you can see them! Notice that whenever you submit a new message, the existing messages get automatically reloaded - so that the new message appears in the list. That’s the magic of React Query.
Deleting messages
The last missing step is the ability to delete messages. Once again, we first need to add this function to
src/appwrite.js
:
// other code export const deleteMessage = async (id) => { await databases.deleteDocument(database, collection, id); };
Enter fullscreen mode Exit fullscreen modeAnd then, all that’s left is adding one more mutation and the
onClick
action on the delete button to thesrc/pages/index.js
page. Don’t copy the whole component. Only copy the added delete mutation and the new return statement. Also, make sure to importdeleteMessage
from AppWrite.
import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import { addMessage, deleteMessage, getMessages } from '@/appwrite'; export default function Home() { // other code const deleteMessageMutation = useMutation(deleteMessage, { onSuccess: () => { queryClient.invalidateQueries('messages'); }, }); return (
Message board
-
{messages?.map((message) => (
-
{message.message}
deleteMessageMutation.mutate(message.$id)} className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md" > Delete
))}
Enter fullscreen mode Exit fullscreen modeOnce again, after successfully deleting a message we tell React Query to refetch all messages so that the deleted message disappears from the list.
Authentication
To add authentication, we first need some additional actions in the
src/appwrite.js
file. This is so that users can sign up and afterward sign in. Also, we need a sign out action and a function to fetch the user session. To store the user session, also create a new React Context in this file. Make sure to importcreateContext
fromreact
.
// other code export const signUp = async (email, password) => { return await account.create(ID.unique(), email, password); }; export const signIn = async (email, password) => { return await account.createEmailSession(email, password); }; export const signOut = async () => { return await account.deleteSessions(); }; export const getUser = async () => { try { return await account.get(); } catch { return undefined; } }; // import createContext from react beforehand export const UserContext = createContext(null);
Enter fullscreen mode Exit fullscreen modeWe now need to fetch the user automatically on every application start and make it available through our context. We’ll do this in
src/pages/_app.js
:
import { useEffect, useState } from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { UserContext, getUser } from '@/appwrite'; import '@/styles/globals.css'; export default function App({ Component, pageProps }) { const [user, setUser] = useState(null); const queryClient = new QueryClient(); useEffect(() => { const user = async () => { const user = await getUser(); if (!user) return; setUser(user); }; user(); }, []); return ( ); }
Enter fullscreen mode Exit fullscreen modeThen we can build the masks for sign up. For this add a new file
src/pages/signup.js
with the following content:
import { useRouter } from 'next/router'; import { useState } from 'react'; import { signUp } from '@/appwrite'; export default function SignUp() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const router = useRouter(); const handleSubmit = async (e) => { e.preventDefault(); try { await signUp(email, password); router.push('/'); } catch { console.log('Error signing up'); } }; return (
Email setEmail(e.target.value)} />Password setPassword(e.target.value)} />{/* Submit button */}Sign UpEnter fullscreen mode Exit fullscreen modeAnd sign in at
src/pages/signin.js
:
import { useRouter } from 'next/router'; import { useState } from 'react'; import { signIn } from '@/appwrite'; export default function SignIn() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const router = useRouter(); const handleSubmit = async (e) => { e.preventDefault(); try { await signIn(email, password); router.push('/'); } catch { console.log('Error signing in'); } }; return (
Email setEmail(e.target.value)} />Password setPassword(e.target.value)} />Sign InEnter fullscreen mode Exit fullscreen modeSigning out
Now, for the last piece of the user authentication, we need to hook up the sign out functionality. We already have all the pieces for this. We just need to check if the
user
session exists and if yes, display a “Sign Out” button that calls thesignOut
function from AppWrite. We’ll change thesrc/pages/index.js
file to this:
import { useContext, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import { addMessage, getMessages, deleteMessage, UserContext, signOut } from '@/appwrite'; export default function Home() { const [input, setInput] = useState(''); const queryClient = useQueryClient(); const { user, setUser } = useContext(UserContext); const { data: messages } = useQuery('messages', getMessages); const addMessageMutation = useMutation(addMessage, { onSuccess: () => { setInput(''); queryClient.invalidateQueries('messages'); }, }); const deleteMessageMutation = useMutation(deleteMessage, { onSuccess: () => { queryClient.invalidateQueries('messages'); }, }); const handleSignOut = async () => { await signOut(); setUser(null); }; return (
Message board
-
{messages?.map((message) => (
-
{message.message}
deleteMessageMutation.mutate(message.$id)} className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md" > Delete
))}
Enter fullscreen mode Exit fullscreen modeAccess control
With authentication implemented, we now want to ensure that only authenticated users can create new messages. And that users can only delete their own messages. So let’s go back to the dashboard and change the
messages
schema permissions to this:As you can see, unauthenticated users can only read messages from now on. If you want to create messages, you need to be authenticated. But how can we enable users to delete their own messages? We’ll do this on a per-document basis. Think of a document as one row in the database. So for that, on the same screen, also make sure you turn on “Document Security”.
Back in the code, modify the
addMessage
function insrc/appwrite.js
as follows. Also, don’t forget to update the import statement as shown.
import { Account, Client, Databases, Permission, Role, ID } from 'appwrite'; export const addMessage = async ({ message, userId }) => { await databases.createDocument( database, collection, ID.unique(), { message, }, [Permission.delete(Role.user(userId))] ); };
Enter fullscreen mode Exit fullscreen modeThat way, we explicitly state that only the owner of the document can delete it. Now, in
src/app/index.js
in the add message button action, also pass theuser.$id
of our current user, so AppWrite knows which user we want to give delete permission to:
addMessageMutation.mutate({ message: input, userId: user?.$id })} className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md mt-4" > Submit message
Enter fullscreen mode Exit fullscreen modeNow, create a new user on
/signup
and then use the same credentials on/signin
to sign in. Afterward, you will be able to create new messages linked to your account. Try deleting those messages and see what happens.Showing the auth state in the UI
Great! Now only owners of messages can delete them. But we’re still showing a delete button beside every message. No matter which user we’re logged in with. Same for the submit a new message field which is visible for unauthenticated users. So we need to change our UI only to show these options when they can actually be performed by the user. For the submit a new message form that’s simple. We’ll just show it whenever a user is logged in:
{user && ( <> setInput(e.target.value)} className="w-full border border-gray-300 rounded-md p-2 mt-4" /> addMessageMutation.mutate({ message: input, userId: user?.$id })} className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md mt-4" > Submit message Sign out > )}
Enter fullscreen mode Exit fullscreen modeIt’s a bit more complicated for checking if a user owns the message. Every message comes with a permissions array. If the user owns that message, their
user.$id
will be present in thedelete
permission field. We’ll write a function to check for that:
const canDelete = (userID, array) => { return array.some((element) => element.includes('delete') && element.includes(userID)); };
Enter fullscreen mode Exit fullscreen modeAnd then check for that in the UI for each message to see if we need to render the delete button:
{messages?.map((message) => (
{message.message}
{canDelete(user?.$id, message.$permissions) && ( deleteMessageMutation.mutate(message.$id, user.$id)} className="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-md" > Delete )}Enter fullscreen mode Exit fullscreen modeWrapping up
And with this, we’re done! Users will now only see the actions they actually have permission for. As this tutorial showed, Next.js, AppWrite, and React Query played very nicely together. Thanks to AppWrite in combination with React Query we get an amazing full-stack development experience without writing any backend code.
We hope you learned something new while following this tutorial. The application we built can serve as a starting point for your own project. If you want to continue building this message board, here are a few feature ideas:
- Add comments to messages. This will teach you how to do relations in AppWrite.
- Add infinite scrolling or pagination to deal with a large number of messages that cannot be rendered all at once.
- Use Next’s Server Side Rendering to fetch and render messages on the server instead of the client.
Regarding deployment, we already set up the AppWrite in a neatly packaged docker-compose setup. You could host this on popular Cloud Providers such as Google Cloud or AWS. As a bonus, add Next.js into the Docker environment using this guide by Vercel.
And while you’re at it, you can also set up preview environments for your project using Preevy. This will easily provision preview environments for your application that you can share with others to get quick feedback and keep your development workflow moving at a good clip.
If you don’t want to go down the self-hosting route, AppWrite also offers a cloud service where that host it for you. But then you’ll need to host the Next.js separately on Vercel or Netlify.
Hope you enjoyed and found this guide helpful.
Good luck!
-
-
In this article, we will look at how to build a simple message board web app with Next.js and AppWrite. In the end, we’ll have a working web app where users can authenticate, post their messages and read the messages of others. I worked with developers on my team to put this guide together. We think this is a good context for learning how to better use these tools and to build something useful along the way.
-
InfluxDB
Collect and Analyze Billions of Data Points in Real Time. Manage all types of time series data in a single, purpose-built database. Run at any scale in any environment in the cloud, on-premises, or at the edge.