Technical Aspects of Bytes 🎉
A Platform to share bite-sized learnings
❕ ⚠️ ⚠️ This project is no longer maintained or hosted. The code 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
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
1export type UserSchema = { 2 uid: string; 3 email: string; 4 name: string; 5 username: string; 6 verified: boolean; 7 __createdtime__?: string; 8 __updatedtime__?: string; 9};
Post Schema
1export type PostSchema = { 2 pid: string; 3 images: Array<string>; 4 slug: string; 5 title: string; 6 uid: string; 7 reactions: number; 8 __createdtime__?: string; 9 __updatedtime__?: string; 10};
Tag Schema
1export type TagType = { 2 tid: string; 3 name: string; 4 color: string; 5 image?: string; 6 slug?: string; 7 __createdtime__?: string; 8 __updatedtime__?: string; 9};
Post_Tag Schema
1export type Post_Tag_Type = { 2 pid: string; 3 ptid: string; 4 tid: string; 5 __createdtime__?: string; 6 __updatedtime__?: string; 7};
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
1-- Gets all the Posts of a User 2SELECT 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 aspid
and tag id astid
-
So now to get a post with all it’s tags, I would write this SQL query
1-- Gets all Tags for a Post 2SELECT t.* FROM bytes.post_tag AS pt INNER JOIN bytes.tag AS t ON pt.tid=t.tid WHERE pt.pid='${post.pid}'
1-- Get all Posts of a Tag 2SELECT 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”
1// useRequireLogin.tsx 2const router = useRouter(); 3 4const { user, setUser } = useContext(UserContext); 5const [loading, setLoading] = useState(false); 6 7useEffect(() => { 8 setLoading(true); 9 firebase.auth().onAuthStateChanged(async (user) => { 10 if (user) { 11 const authUser = await getAuthUserFromHarper(user.uid); 12 setUser({ ...authUser, isLoggedIn: true }); 13 setLoading(false); 14 router.push(to); 15 } else { 16 setUser({}); 17 setLoading(false); 18 router.push("/login"); 19 } 20 }); 21}, []); 22 23return { user, loading }; 24 25// ------------------------ 26// Using in the Upload Page 27// /pages/upload.tsx 28// ------------------------ 29 30const { user, loading } = useRequireLogin({ to: "/upload" }); 31 32if (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 - @verma__shubham