devblog

Intermediate
  • Async/Await
  • API
  • JSON
  • axios
  • React
  • #2PlaysAMonth
avatar
AmrinFeb 19, 2023
A dev to client. Read dev to blog in a new frontend
yOU DON'T nEEd a fRamEWoRk

yOU DON'T nEEd a fRamEWoRk

#fullstack
#alpinejs
#javascript
#webdev

Every so often there's an article I'll read that's like, "You don't need a JavaScript Framwork! Just use vanilla JS, it's so much better! Why can't we all just use jQuery again?" Obviously these people are intelligent in their own right, but I respectfully but strongly disagree. In this blog post, I'll share my experience building a fullstack CRUD chat app with Bootstrap, Alpine.js, and Pocketbase. (Spoiler alert: It f██kin' sucks)

Code: https://github.com/Gravy59/petite-fullstack

Edit 3/7/23: It seems that I haven't been clear in this article, I decided to create a fullstack application in vanilla not to show that "yOU DON'T nEEd a fRamEWoRk" but to show how difficult it is in relation to something like react. And still, I was not using vanilla. I was using Alpine, which is still a framework. I hope y'all understand.

Getting Started

To get started, I created a new project folder and created an empty html file. I chose not to use npm and instead opt for link and script tags from CDNs. I threw in Bootstrap and Alpine.js. Alpine is basically the modern version of jQuery, and I'm more comfortable with Alpine from the copious amount of classes my school had on it that one year. Bootstrap provided me with a pre-built user interface that I could customize, and Alpine.js allowed me to add interactivity to my app.

For the back-end, I chose Pocketbase, which is a cloud-hosted database service that can be accessed through a simple REST API. Pocketbase is easy to use and integrates well with front-end frameworks like Bootstrap and Alpine.js, making it a great choice for this project.

Creating the User Interface

After setting up the project, I started creating the user interface using Bootstrap. I designed a simple chat interface with a list of messages on the top and a message input field on the bottom. In about five minutes I had a basic chat interface without any interactivity.

A basic chat interface similar to Discord

Next, I added tried to add interactivity to the interface using Alpine.js. Full stop here, I'm not a programming God. I don't know too much about data fetching with vanilla JS. After pouring to the Alpine docs for a bit longer than I want to tell you, I settled on a function called pageData that would fetch data, subscribe to messages, and expose important variables to the client. I also bound the chat form with x-model to the text variable on my pageData function. This is important because Alpine has a weird form paradigm. Now, I can use @submit.prevent to run a function on pageData to grab this.text and submit it to pocketbase. As a final touch, I used x-transition for some smoother animation.

Data modeling

Pocketbase is awesome because it allows me to abstract security rules. I created a messages collection with one column: text. Pocketbase automagically sets an id, relation to a user, and creation/update dates. In my API rules, I unlocked List/search, view, create, update, and delete. For creating, updating, and deleting, it's important to restrict those actions to the user who created the message. I copied user.id = @request.auth.id to all of these. This forces PB to only allow logged in users to send messages and only allows the creator of a message to edit or delete.

User Authentication

User Auth with PB's SDK is easy. It saves sessions in localstorage and verifies them with the server. All I needed to do was use Alpine's x-show directive to show and hide login, sign up, and the actual app based on the authentication state.

R & D

For reading messages, I had to subscribe to them to get realtime communication. To do that, I used PB's SDK to subscribe and add a callback function to determine what to do with the UI when a message was changed.

client
    .collection("messages")
    .subscribe("*", async ({
        action,
        record
    }) => {
        if (action === "create") {
            const user = await client.collection("users").getOne(record.user);
            record.expand = {
                user: [user]
            };
            this.messages.push(record);
        }
        if (action === "delete") {
            this.messages = this.messages.filter((m) => m.id !== record.id);
        }
    });
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

Here's the deal: Yes, it's possible to not use a framework. Is it easy? HELL NO! Personally, If you want some level of interactivity that needs dynamic UIs, you can try to use something like Alpine or petite-vue, but if you need more than that, something like svelte or anything from pnpm create vite will make your life so much easier.

Thanks for reading, y'all!