Technical Aspects of Bytes

⚠️⚠️ Project is no longer maintained or hosted. The code is available on Github

If you are unaware of Bytes then check out this blog that I wrote:
Bytes - A Platform to share bite-sized learnings!

In this blog, I'll talk about the decision to choosing the tech stack, the database modeling of Bytes
Table of Contents
Tech Stack
  • NextJS
  • HarperDB
  • TailwindCSS
  • Firebase
  • NextJS
  • I chose to go with NextJS because it provides SSR support and Pages support out of the box.
  • HarperDB
  • Since Bytes is made as a part of the HashNode-HarperDB hackathon, HarperDB acts as the Database for Bytes.
  • The ability to queries in SQL way or NoSQL ways make it so easy to work with it.
  • TailwindCSS
  • I always praise Tailwind in my blogs, It's easier to write CSS with all the utilites classes
  • It also has Dark/Light theme support.
  • Firebase ( Storage and Authentication )
  • Images for Bytes are handled by Firebase storage.
  • All the URLs of the uploaded image are then stored in Harper
  • Authentication is implemented using Firebase authentication as it handles also the user sessions gracefully.
  • Database Modelling
  • This is the fun part of working with databases for me. In this section, I'll share the schemas of the tables and their relationships.
  • I have tried to keep the models and their relationships normalized.
  • Schemas
    User Schema
    export type UserSchema = {
      uid: string;
      email: string;
      name: string;
      username: string;
      verified: boolean;
      __createdtime__?: string;
      __updatedtime__?: string;
    };
    Post Schema
    export type PostSchema = {
      pid: string;
      images: Array<string>;
      slug: string;
      title: string;
      uid: string;
      reactions: number;
      __createdtime__?: string;
      __updatedtime__?: string;
    };
    Tag Schema
    export type TagType = {
      tid: string;
      name: string;
      color: string;
      image?: string;
      slug?: string;
      __createdtime__?: string;
      __updatedtime__?: string;
    };
    Post_Tag Schema
    export type Post_Tag_Type = {
      pid: string;
      ptid: string;
      tid: string;
      __createdtime__?: string;
      __updatedtime__?: string;
    };
    Relationships
    User <-> Post
  • This relationship would be one-to-many
  • So the Post Schema would have a foreign key i.e User's uuid as uid
  • To get a user posts, I would just inner join User and Post on this uid
  • -- Gets all the Posts of a User
    SELECT p.*,u.name,u.username FROM bytes.post AS p INNER JOIN bytes.user AS u ON u.uid=p.uid WHERE u.username='${username}'
    Post <-> Tags
  • The relationships b/w Post and Tags would be many-to-many.
  • One post can have many tags and One tag can have posts
  • I found this amazing article that shows various ways on implementing N:M relationships
  • Based on it, I made separate table called Post_Tag that would contain a post id as pid and tag id as tid
  • So now to get a post with all it's tags, I would write this SQL query
  • -- Gets all Tags for a Post
    SELECT t.* FROM bytes.post_tag AS pt INNER JOIN bytes.tag AS t ON pt.tid=t.tid WHERE pt.pid='${post.pid}'
    -- Get all Posts of a Tag
    SELECT p.*,u.name,u.username FROM bytes.post_tag AS pt INNER JOIN bytes.post AS p ON pt.pid=p.pid INNER JOIN bytes.user AS u ON p.uid = u.uid WHERE pt.tid='${tag.tid}'
    Protecting Routes
  • At the moment, Bytes has the following routes:
    • /
    • /upload
    • /login
    • /profile/:id/
    • /byte/:id/
    • /tag/:id/
  • Out of this routes, the /upload route is protected, It can be only accessed if the user is logged in.
  • So to do so, I made a custom hook that would check for user.
  • If user is logged in, it allows the user else redirect to "/login"
  • // useRequireLogin.tsx
    const router = useRouter();
    
    const { user, setUser } = useContext(UserContext);
    const [loading, setLoading] = useState(false);
    
    useEffect(() => {
      setLoading(true);
      firebase.auth().onAuthStateChanged(async (user) => {
        if (user) {
          const authUser = await getAuthUserFromHarper(user.uid);
          setUser({ ...authUser, isLoggedIn: true });
          setLoading(false);
          router.push(to);
        } else {
          setUser({});
          setLoading(false);
          router.push("/login");
        }
      });
    }, []);
    
    return { user, loading };
    
    // ------------------------
    // Using in the Upload Page
    // /pages/upload.tsx
    // ------------------------
    
    const { user, loading } = useRequireLogin({ to: "/upload" });
    
    if (loading || !user.isLoggedIn) return null;
    Hope you liked this blog and learned something from it.
    There are still some improvements and features that I am adding to Bytes.
    Socials
    You can follow me on my Twitter - @Shubham_Verma18

    29

    This website collects cookies to deliver better user experience

    Technical Aspects of Bytes