DEV Community

Cover image for How Builtwith.appwrite.io Was Made
haimantika mitra for Appwrite

Posted on

How Builtwith.appwrite.io Was Made

We recently announced the builtwith.appwrite.io website - the one-stop solution to create awareness about your Appwrite-related projects as well as discover the amazing work being done by our community members.

In this article, we will see how the website was built. If you want to learn more about the website and what features it has, read the announcement blog here.

🤔 Why Did We Build Built with Appwrite?

The vision around builtwith.appwrite.io was to highlight our community members who are building with Appwrite and aimed to provide developers with a playground where they could explore real-world examples, learn best practices, and ignite their imagination for building feature-rich applications. The website is now accepting submissions of Appwrite-built projects, currently consisting of around 50 community-contributed projects. If you are looking for some ideas, you can use our filtering options to discover new projects based on Appwrite services, frameworks, UI libraries, or specific use cases.

Screenshot of the website

🎒Tech Stack

We leveraged the power of Appwrite Cloud for our backend, incorporating seamless authentication through one of Appwrite's OAuth providers, GitHub. To store data, we utilized Appwrite's Storage and Database. Security was enhanced using Appwrite Functions during submission and upvoting. For a streamlined frontend experience, we turned to Qwik, while the elegant Pink Design served as our trusted design system.

The combination of Qwik, Appwrite, and Pink Design transformed the development process into an enjoyable and efficient journey, making this project both delightful and time-efficient.

Setting up the server and client

Setting up the server is really simple, all you need to do is:

  • Register Appwrite account
  • Create a project
  • Setup GitHub authentication, a database, and a storage bucket
  • Setup Appwrite functions

Note: You can use the Appwrite CLI for easy and quick setup

On the client side, you need to:

  • Install libraries with npm install
  • Update projectID in src/AppwriteService.ts
  • Start server npm run dev

For detailed steps, you can follow our GitHub README.

Functions Highlight 🔦

We wanted to add more security to the website and also prevent spam entries at the same time, and this is when Appwrite Functions came to the rescue.

A. Submitting project - We wanted to review each submission and then add them to the website. This needed an extra step of approval and making sure that all the necessary details were added. We also had to make sure that the projects collection did permit users to CREATE, and the only way to do it is over a function.

It ensures that people cannot set “upvotes” to fake huge numbers, and neither can they set isPublished=true, and skip the verification step.

We also wanted to send an email to the Appwrite core team whenever there is a new submission.
Here is how the email looks 👇

Screenshot of emails

The following code ensures that all the necessary fields are added and by default, marks each project submission as isPublished=false and upvotes: 0.

const sdk = require("node-appwrite");
const nodemailer = require("nodemailer");

module.exports = async function (req, res) {
  const client = new sdk.Client();
    const databases = new sdk.Databases(client);

    client
        .setEndpoint("https://cloud.appwrite.io/v1")
        .setProject(req.variables["APPWRITE_FUNCTION_PROJECT_ID"])
        .setKey(req.variables["APPWRITE_FUNCTION_API_KEY"]);

    const payload = JSON.parse(req.payload ?? "{}");
    const userId = req.variables["APPWRITE_FUNCTION_USER_ID"] ?? "";

    const project = await databases.createDocument(
        "main",
        "projects",
        sdk.ID.unique(),
        {
            isPublished: false, // Enforce
            isFeatured: false, // Enforce
            upvotes: 0, // Enforce
            platform: payload.platform,
            name: payload.name,
            tagline: payload.tagline,
            imageId: payload.fileId,
      // And others...
        });

    res.json({
        ok: true,
        msg: "Submission successful.",
    });

    const approverEmails = req.variables["APPROVER_EMAILS"].split(",");
    const transporter = nodemailer.createTransport({
        // Configuration of SMTP
    });

    await transporter.sendMail({
        from: SMTP_USERNAME,
        to: approverEmails,
        subject: "Project Submission - builtwith.appwrite.io",
        text: `Project "${project.name}" has been submitted.`
    });
};
Enter fullscreen mode Exit fullscreen mode

B. Upvoting - The website has an upvote feature, but we wanted to ensure it is not misused by fake IDs. This was avoided by not giving CREATE permission to users, and the only way to achieve this is by using a function.

Why did we not delegate create permission? 👀
To prevent people from making multiple upvote documents and having no way to validate those

The following code expects string input of the project ID that the user wants to upvote and ensures that the same user cannot upvote the same project multiple times. It then also aggregates the vote count and updates the project document with a new count.

const sdk = require("node-appwrite");

module.exports = async function (req, res) {
  const client = new sdk.Client();

  const databases = new sdk.Databases(client);

  client
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject(req.variables["APPWRITE_FUNCTION_PROJECT_ID"])
    .setKey(req.variables["APPWRITE_FUNCTION_API_KEY"]);

  const projectId = req.payload ?? "";
  const userId = req.variables["APPWRITE_FUNCTION_USER_ID"] ?? "";

  const search = await databases.listDocuments("main", "projectUpvotes", [
    sdk.Query.limit(1),
    sdk.Query.equal("projectId", projectId),
    sdk.Query.equal("userId", userId),
  ]);
  const isUpvoted = search.documents.length > 0;

    // Mark upvoted / not-upvoted
  if (isUpvoted) {
    await databases.deleteDocument("main", "projectUpvotes", search.documents[0].$id);
  } else {
    await databases.createDocument("main", "projectUpvotes", sdk.ID.unique(), {
      projectId,
      userId,
    });
  }

    // Re-count total votes
  const aggregation = await databases.listDocuments("main", "projectUpvotes", [
    sdk.Query.limit(1),
    sdk.Query.equal("projectId", projectId),
  ]);

  const totalUpvotes = aggregation.total;

  await databases.updateDocument("main", "projects", projectId, {
    upvotes: totalUpvotes,
  });

  res.json({
    ok: true
  });
};
Enter fullscreen mode Exit fullscreen mode

🐞Blooper

What bug did we handle? 🐛

Soon after we launched the Built With Appwrite website, we received bug reports from our community members about not being able to login. The search began to see what went wrong, and it turned out that the search dialog didn’t work as expected.

What happened? 🤔

When the dialog was added, all the code that was in useVisibleTask stopped working, including login and some global keyboard shortcuts.

Why did it happen? 🙅‍♀️

While building, we assumed the useVisibleTask() hook was like react useEffect() hook. We later figured it only runs when the component is visible using Intersection observer API.

How did we solve it? 👀

It took lots of debugging by reverting each individual change to figure out the issue. Eventually, we noticed that if we kept everything the same - but instead used a div rather than a dialog, things worked again!

P.S. - Adding the entire code would make the article very long. However, if you're curious about the implementation details of a specific feature, you can see the full source code here

🔗 Learn More

Now that you know how Appwrite and Qwik were used to build the entire website, here are some resources that can come in handy.

Appwrite Resources

Qwik Resources

Top comments (1)

Collapse
 
bigfish258 profile image
hutu zhu

awesome !