Skip to main content

Command Palette

Search for a command to run...

TownSquare: An Open-Source Forum for a Small Town

Updated
6 min read

TownSquare: Appwrite Hashnode Hackathon

Team Details

  • BoonKai - @bk7312

Description of Project

TownSquare is an open-source forum designed using React and Tailwind CSS on the front-end, Appwrite Cloud on the back-end, and deployed using Vercel. It offers an online community for any small town or village to deploy so they can keep up-to-date with the latest happenings in town.

Why an Open-Source Forum?

With the advent of the internet and social media, most people now interact on a global scale and are no longer bound by geography. Someone from New York can easily interact with their friends from Paris or Istanbul, and online communities of similar interests are also quite common, resulting in people interacting more with communities from far away than those next to them.

So what happens in a small town when everyone hides behind their phone chatting mostly with friends from far away? What happens to a town's economy when most of its residents shop online and work remotely? How do we foster a sense of community and improve the economy in a small town?

Which is where the idea of building TownSquare came from. What if we have an online community where residents of a small town or village can gather? A sort of digital town square, a place where residents can keep up-to-date on what's happening around town, ask for help, or look for local businesses in the area. Will this solve the problem? Perhaps, but it will require the town to play an active part in promoting, maintaining, and updating the community in TownSquare as well.

Side note: Technically, all this can be done in a Facebook group or by using existing open-source forums, such as the classic phpBB or the popular Discourse. Also, TownSquare is still mostly an unpolished demo app from a one-person team and can't compete with existing forum solutions.

Tech Stack

  • React: The project was developed using React and React Router 6, utilizing the new data API, and bundled together using Vite.

  • Tailwind CSS: The styling was done using Tailwind CSS to design and customize the UI.

  • Appwrite Cloud: The back-end was developed using Appwrite Cloud to handle user authentication, database read/write, and file storage for storing user profile pictures. Interactions with the backend were done using the Web SDK.

    • Authentication: The Account API was used to handle user authentication, it was very convenient and worked right out of the box.

    • Database: The Database API was used to handle read and write operations to the database, it took some time to structure the forum on a mostly flat collection/document structure but using the API was quite simple.

    • Storage: The Storage API was used to handle and store user profile pictures.

  • Vercel: The project was deployed live on Vercel.

Challenges Faced

There were many challenges and struggles when building this project, both on the front-end and back-end, and as a relatively new developer, it was good to go through the learning curve.

Challenges with using Appwrite Cloud

The biggest challenge was structuring the database. My prior experience with back-end was with Firebase Realtime database, which is mostly just storing data as JSON without any pre-defined structure. With the Appwrite database, I had to learn how to work with collections/documents in a mostly flat database structure.

Structuring the Database

So how do I structure a database to store five forum sections, each section holding its own set of threads, each thread having its own set of posts/replies? The nature of a collection/document structure is also such that each collection must have a pre-defined attribute, and each document must follow that data structure.

My first attempt was to store each forum section as a collection such that each forum thread is a document. In each collection, I would define the following attributes:

title: String (thread title)
posts: Array of Strings (array of forum posts)
pin: Boolean

However, there's one big problem. If a user wanted to post a reply, I would have to send the entire posts array (which can get quite large for a long thread) to update the thread document, and that's sending a lot of redundant data.

My second attempt was to store each forum section as a database and use a collection for each forum thread. Then, each post would be stored as a document. This works okay but I would then need a collection master list to keep track of the individual collectionID for each forum thread. Also, I would need to use the Server SDK to create new collections, either via Vercel serverless functions or Appwrite functions. After playing around with it, I decided to restructure again.

My third (and current) attempt was to store each forum section into two separate collections. The first collection will hold the list of all threads while the second collection will hold the list of all posts. I would use Query in the database API call to get the relevant posts from the post collection instead of keeping a separate collection to hold the posts from each thread.

function getThreads(col) {
    return database.listDocuments(Server.database, Server[col].list)
}

async function getPosts(col, doc) {
    const opening = await database.getDocument(
        Server.database, 
        Server[col].list, 
        doc
    )
    const replies = await database.listDocuments(
        Server.database, 
        Server[col].post, 
        [
            Query.equal("threadID", doc),
            Query.orderAsc("$createdAt")
        ]
    )
    const returnObj = {
        opening,
        replies
    }
    return returnObj
}

I could further simplify and store all the forum sections into two collections, one for the thread list and the other for the post/reply list, and just use Query to filter out each forum section. However, I was planning to add unique features and attributes, such as a date/time attribute for the "Event" section and an issueOpen/Closed attribute for the "Help Wanted" section, so that users can sort/filter by those attributes. Thus, it was better to keep the collection separate for now.

Handling User Profile

One thing I found was that there was no way to use usernames for login, and unlike the user's email property, the name property doesn't have to be unique. For a forum software where I have to display the user's name (probably not a good idea to display the user's email), I had to create a separate collection to check for unique names and ensure there are no duplicate names in use. This came in handy as I can use that collection to store the user's bio and profile picture as well. Storing in user preference doesn't work as other users have to be able to access them as well.

Using File Buckets for Profile Picture

One bug I found was that storage.getFileView isn't able to display the profile picture in Safari. I'm not sure why but it works in Chrome-based browsers. See GitHub issue.

For updating the profile pictures, I was hoping storage.updateFile would work but it only updates the permissions(?). So I had to delete the old picture, upload a new picture, and update the picID in the user collection.

Other challenges

I had a few other challenges not related to Appwrite, the biggest was with React Router and related to the nature of SPAs. If I navigate to the login page from the home page, it works fine. But navigating directly to the login page via the login URL and Vercel will throw a 404 error. This is because a login.html page doesn't exist and all routing is handled by React Router. I could probably just redirect to the home page and let the user navigate to the desired page, but that breaks the purpose of sharing links. See GitHub issue. Edit: Fixed.

Styling the app, designing a desktop and mobile layout, and creating a beautiful light/dark mode were also challenges I faced. While the app is responsive, it's mostly still a mobile layout. The styling and the light/dark mode are still pretty basic at the moment.

Public Code Repo

https://github.com/bk7312/appwrite-hackathon-2023

Link to app: https://town2.vercel.app/