DEV Community

Cover image for JStack + Appwrite: A Match Made in Heaven for Modern Web Development
Chirag Aggarwal
Chirag Aggarwal Subscriber

Posted on • Edited on

JStack + Appwrite: A Match Made in Heaven for Modern Web Development

If I had a penny for each time a youtuber has launched his own tech stack, I would have 2 pennies, which isn't much but its weird that it happened twice.

I am talking about T3 Stack launched a while back by everyone's favourite Theo, but recently a new player has entered the market called JStack, by Josh who is the lead Devrel at Upstash. To be fair, its not even that recent, but as always I am late to the party I try to give a framework time to mature and gather feedback from community before giving it a shot.

So, did I prefer JStack over T3 stack? Did it have more compatibility with Appwrite, my favourite backend provider? Can it be hosted on Appwrite Sites? Let's find out.

Getting Started

Let's start with initialising the project:

bunx create-jstack-app@latest
Enter fullscreen mode Exit fullscreen mode

Options selected:

┌   jStack CLI 
│
◇  What will your project be called?
│  testing-jstack-appwrite
│
◇  Which database ORM would you like to use?
│  None
│
◇  Should we run 'bun install' for you?
│  Yes

Using: bun

✔ testing-jstack-appwrite scaffolded successfully!
Enter fullscreen mode Exit fullscreen mode

Let's just quickly run a dev server to see what we get out of the box:

cd cd testing-jstack-appwrite
bun dev
Enter fullscreen mode Exit fullscreen mode

JStack landing page

Initialising Project

On skipping the ORM option, the stack still sets up a /src/server folder with an example posts router. But it only mocks the DB using an array which does not persist:

// Mocked DB
interface Post {
  id: number
  name: string
}

const posts: Post[] = [
  {
    id: 1,
    name: "Hello World",
  },
]
Enter fullscreen mode Exit fullscreen mode

We skipped the ORM option because Appwrite provides built-in schema management through its SDK, eliminating the need for a separate ORM layer. To get started, let's head over to https://cloud.appwrite.io to set up our project.

If you are using Appwrite for the first time, I highly recommend you check out our Start with Web docs.

Here is a quick setup guide:

  • Create a new project -

New project model (Appwrite)

  • Add a new web platform and select Next.js -

New platform screen (Appwrite)

  • Go to project overview and grab your project's ID and region-specific endpoint:

Project ID and endpoint (Appwrite)

  • Create a new .env file in your project directory and paste in those values:
NEXT_PUBLIC_APPWRITE_PROJECT_ID=686a271700323696d223
NEXT_PUBLIC_APPWRITE_ENDPOINT=https://fra.cloud.appwrite.io/v1
NEXT_PUBLIC_APP_DOMAIN=localhost # we will change it later
Enter fullscreen mode Exit fullscreen mode
  • Now, let's initialise the Appwrite SDK in the project using:
bun add appwrite
Enter fullscreen mode Exit fullscreen mode

This covers up on how to initialize Appwrite in normal Next.js project. Now we need to configure our Appwrite databases according to the project. For this demo, let's create a Posts collection to shift the mock database JStack uses as an example to Appwrite.

Defining Schema

  • Go to the Appwrite Console > Databases > Create Database. We will call it main , and keep it' ID as main as well:

Create database (Appwrite)

  • Similarly, create a collection posts and keep its ID as posts.
  • Each document in Appwrite already has an unique ID attached to it, so the only attribute we need is name for now:

Create string attribute (Appwrite)

  • Lastly, you will need to define who can access your collection. To learn more about it, check out the docs for Appwrite Permissions. For now, we will set it to any:

Create permissions

Syncing Types

That's it, we are all set with configuration on the Appwrite Console. Now let's set it up in our project. I will also take the help of the Appwrite CLI to help me set up things faster. You can learn more about how to install it by following the installation docs.

Once done, run:

appwrite init project

? How would you like to start? Link directory to an existing project
? Choose your organization 67610b8ee51f147ca943
? Choose your Appwrite project. [object Object]
✓ Success: Project successfully linked. Details are now stored in appwrite.json file.
Would you like to pull all resources from project you just linked? Yes
Enter fullscreen mode Exit fullscreen mode

Once done, let's utilize the last Types Generation feature to sync our defined types:

appwrite types src/types       

ℹ Info: Detected language: ts
ℹ Info: Directory: src/types does not exist, creating...
ℹ Info: Found 1 collections: posts
ℹ Info: Found 1 attributes across all collections
ℹ Info: Added types to src/types/appwrite.d.ts
✓ Success: Generated types for all the listed collections
Enter fullscreen mode Exit fullscreen mode

Result will look something like this:

import { type Models } from 'appwrite';

/**
 * This file is auto-generated by the Appwrite CLI. 
 * You can regenerate it by running `appwrite types -l ts src/types`.
 */

export type Posts = Models.Document & {
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

Obviously pretty small for now, but a really helpful feature once the project expands and more collections are defined.

Configuring Code

Final steps are to connect the Appwrite backend with our tech stack. For that let's create a simple appwrite.ts file in src/lib folder:

import { Client, Databases, ID } from "appwrite";

const client = new Client()
    .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!)
    .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!);

const databases = new Databases(client);

export { client, databases };
Enter fullscreen mode Exit fullscreen mode

Now, we can modify the original post-router.ts file to use the defined database:

import { z } from "zod";
import { j, publicProcedure } from "../jstack";
import { databases } from "@/lib/appwrite";
import { type Posts } from "@/types/appwrite";
import { ID } from "appwrite";

const DATABASE_ID = "main";
const POSTS_COLLECTION_ID = "posts";

export const postRouter = j.router({
  recent: publicProcedure.query(async ({ c }) => {
    const posts = await databases.listDocuments<Posts>(
      DATABASE_ID,
      POSTS_COLLECTION_ID,
    );
    return c.superjson(posts.documents.at(-1) ?? null);
  }),

  create: publicProcedure
    .input(z.object({ name: z.string().min(1) }))
    .mutation(async ({ c, input }) => {
      const post = await databases.createDocument<Posts>(
        DATABASE_ID,
        POSTS_COLLECTION_ID,
        ID.unique(),
        {
          name: input.name,
        },
      );

      return c.superjson(post);
    }),
});
Enter fullscreen mode Exit fullscreen mode

And done! Now your JStack application is using Appwrite as its backend provider.

Testing

  • Start the dev server if not already by running:
bun run dev
Enter fullscreen mode Exit fullscreen mode
  • Your application should have started on: http://localhost:3000/
  • Create a new post.
  • You should be able to see it in recent posts:

Recent posts image

  • Also, the data should be visible in your Appwrite Console:

Appwrite console showing data

Deploying your application

Until recently, to deploy your Next.js application, you had pretty low choices. But no more Appwrite Sites. Now, you can have your backend and frontend hosted on Appwrite.

Check out these docs on how to get started with Sites - https://appwrite.io/docs/advanced/self-hosting/sites

Let's deploy your application using Sites:

  • Go to Appwrite Console > Sites > Create Site.
  • You can upload a tar file directly, or connect to a GitHub repository (I prefer the GitHub option for automatic deployments).
  • Select the repository:

Selecting repository screen

  • You can keep all the settings as the default ones, just make sure to upload the environment variables we defined earlier:

Add environment variables screen

  • Depending on the domain you are assigned / plan to use, you also need to update the APP_DOMAIN variable we defined earlier. For me I will keep it:
NEXT_PUBLIC_APP_DOMAIN=jstack-appwrite-template.appwrite.network
Enter fullscreen mode Exit fullscreen mode
  • Click on deploy.

And done! Your application should be live 🎉

Deployment successful screenshot

You can checkout the demo application here - https://jstack-appwrite-template.appwrite.network/

Conclusion

Let's start by answering the question the blog began with: Do I prefer JStack over T3 Stack? I'm sorry, Theo, but I do.

JStack solves the fundamental problems I have always had with Next.js:

  • Using Hono instead of Next.js's inbuilt convention for defining API routes.
  • Using TanStack query out of the box. Trust me, you should start now if you are not using it.
  • Type safe and uses Zod.

The most noticeable difference between T3 Stack and JStack is how light they are, mainly due to the inclusion of the tRPC protocol in T3 Stack. Although JStack also uses tRPC, it seems like a much simpler implementation of it. Most projects do not require the complexity tRPC adds, and in my opinion it makes the code 10x more complicated to maintain.

So, give JStack a shot if you are starting with a new project (or want to spend a weekend migrating your existing stack to it). Huge shoutout to Josh for creating this wonderful stack. And lastly, give Appwrite a chance to be your next all in one cloud platform, both for your backend and frontend needs.

Sources:

Top comments (1)

Collapse
 
yashksaini profile image
Yash Kumar Saini

You are not the only who comes late to the party bro,