Logo
Published on

How to Build Your Own Blog(3): Backend API & Frontend

54 Views

Backend API

While I’d prefer to skip this section since the backend APIs are fairly straightforward given our data model and required functionality, I’ll first walk through the list of APIs and then dive into the experience of building the frontend.

Given this data model for posts:

type Post struct {
    ID        string    `json:"id,omitempty"`
    Slug      string    `json:"slug,omitempty"`
    Title     string    `json:"title,omitempty"`
    Author    string    `json:"author,omitempty"`
    Content   string    `json:"content,omitempty"`
    Summary   string    `json:"summary,omitempty"`
    Tags      []string  `json:"tags,omitempty"`
    CreatedAt time.Time `json:"created_at,omitempty"`
    UpdatedAt time.Time `json:"updated_at,omitempty"`
}

The following APIs are provided by the backend service:

1. List Posts

  • Method: ListPosts
  • Request: ListPostsRequest
    • Fields: None
  • Response: ListPostsResponse
    • Fields: Posts (array of Post objects without Content field)
  • Description: Retrieves a list of all blog posts.

2. Get Posts by Tag

  • Method: GetPostsByTag
  • Request: GetPostsByTagRequest
    • Fields: Tag (string)
  • Response: GetPostsByTagResponse
    • Fields: Posts (array of Post objects without Content field)
  • Description: Fetches posts associated with a specific tag.

3. Get Tag Counts

  • Method: GetTagCounts
  • Request: GetTagCountsRequest
    • Fields: None
  • Response: GetTagCountsResponse
    • Fields: TagCounts (map with string keys and int64 values)
  • Description: Returns the number of posts of each tag.

4. Get Post by ID

  • Method: GetPost
  • Request: GetPostRequest
    • Fields: PostID (string)
  • Response: GetPostResponse
    • Fields: Post (single Post object)
  • Description: Retrieves a specific post by its unique PostID.

5. Get Post by Slug

  • Method: GetPostBySlug
  • Request: GetPostBySlugRequest
    • Fields: Slug (string)
  • Response: GetPostBySlugResponse
    • Fields: Post (single Post object)
  • Description: Fetches a specific post by its URL-friendly Slug.

6. Create Post

  • Method: CreatePost
  • Request: CreatePostRequest
    • Fields: Post (single Post object, ID and Slug fields are ignored)
  • Response: CreatePostResponse
    • Fields: PostID (string), Slug (string)
  • Description: Creates a new blog post and returns its unique PostID and Slug.

7. Update Post

  • Method: UpdatePost
  • Request: UpdatePostRequest
    • Fields: OriginalSlug (string), Post (updated Post object, ID and Slug fields are ignored)
  • Response: UpdatePostResponse
    • Fields: None
  • Description: Updates an existing post specified by OriginalSlug.

Given this user data mode:

type User struct {
    Username       string `json:"username,omitempty"`
    HashedPassword []byte `json:"hashed_password,omitempty"`
    Salt           []byte `json:"salt,omitempty"`
    CreatedAt      time.Time `json:"created_at,omitempty"`
    UpdatedAt      time.Time `json:"updated_at,omitempty"`
}

The following APIs are defined for user management:

1. Create User

  • Method: CreateUser
  • Request: CreateUserRequest
    • Fields:
      • Username (string): Unique username for the new user.
      • Password (string): Password for the new user account.
  • Response: CreateUserResponse
    • Fields: None
  • Description: Registers a new user with a username and password.

2. Authenticate User

  • Method: AuthenticateUser
  • Request: AuthenticateUserRequest
    • Fields:
      • Username (string): User's username.
      • Password (string): User's password.
  • Response: AuthenticateUserResponse
    • Fields:
      • Token (string): Authentication token that allows the user to access protected features.
  • Description: Authenticates a user and returns a token for secure access.

Frontend

Before starting this project, my last experience with frontend development was five years ago, when I first learned React. Fortunately, React remains one of the most popular frontend libraries today, which helped make my learning curve a bit easier.

After deciding to use React, my next step was to choose a CSS framework. I didn’t want to spend too much time building CSS from scratch or dealing with conflicts between multiple frameworks. Ideally, I wanted to start with a template project that I could easily adjust to meet my design needs. And luckily, I came across this project - tailwind-nextjs-starter-blog.

Backend Integration

The original tailwind-nextjs-start-blog is a static website using React Server Component and Contentlayer to manage markdown content. To align it with my project, I needed to update it to integrate with my own backend APIs.

In React, components can be categorized as either server or client components based on where they render and how they interact with the user. Below is a comparison table highlighting the key differences between server and client components in React, particularly within frameworks like Next.js:

FeatureServer ComponentsClient Components
Rendering LocationRendered on the serverRendered on the client
InteractivityNon-interactive by defaultFully interactive
Access to Browser APIsNo access (e.g., cannot use window, document)Full access (can use window, document, localStorage)
React HooksCannot use hooks that rely on the client (e.g., useEffect)Can use all hooks, including useEffect, useState
PerformanceFaster initial load, as only HTML is sent to the clientMay increase load time due to client-side JavaScript
JavaScript BundlingDoes not contribute to client-side JavaScript bundle sizeAdds to client-side JavaScript bundle size
Data FetchingData can be fetched directly on the server, before renderTypically fetches data on the client, requiring loading states
Use CasesStatic, non-interactive content like headers, footers, content-heavy pagesDynamic and interactive content, such as forms, buttons, user-controlled states
SEOOptimized for SEO, as content is pre-renderedRequires additional configuration for SEO
Integration with LayoutsTypically used for layouts in Next.js, improving reusability and performanceCan be embedded within server layouts when interactivity is needed
DirectiveDefault in Next.js for files without 'use client'Must include 'use client' directive at the top of the file
Hydration RequirementNo hydration needed, as it’s static HTMLRequires client-side hydration for interactivity

For backend integration, the primary focus is on data fetching. As discussed in the first blog of this series, which covers the architecture, the backend APIs are only accessible to the web server and should not be publicly exposed. Therefore, any components calling backend APIs should be server components. An additional benefit is that server components are optimized for SEO.

Side Note: Another development advantage of using server components is their access to environment variables, since they are rendered on the server. This allows us to isolate the production environment from the local test environment, ensuring, for instance, that test environments don’t have access to production data and that test traffic isn’t recorded by production analytics plugins. With environment variables, we can control server component behavior and make changes by simply updating the variable—no rebuild necessary.

As for client components, here’s a workaround I used: let the server component parent read the environment variable, then pass its value as a prop to the client component.

Enhancements for a Complete Web Experience

With backend integration being done, the website can be launched with full functionality. But a few subtle features is worth mentioning, as they can enhance the overall user experience.

  • Sitemap: A dynamically generated sitemap ensures search engines can easily crawl and index the site, improving discoverability.
  • RSS Feed: For users who prefer to keep up with content through an RSS reader, an RSS feed is available. This allows readers to stay updated with the latest posts without needing to visit the site directly.
  • Open Graph & Twitter Card: These metadata tags are essential for providing rich previews when sharing links on social media. By setting up Open Graph and Twitter Card tags, I ensure that links shared from the site display with attractive titles, descriptions, and images.
  • robots.txt: This file communicates with search engines, indicating which pages or directories should be crawled or ignored. It’s a small detail but contributes to efficient crawling.
  • Favicon: Adding a favicon ensures a recognizable icon appears in the browser tab, bookmarks, and other places where the site is saved. This small visual touch helps with brand identity.
  • SEO Optimizations: Although the primary focus of the site isn’t SEO, thoughtful use of meta tags and keyword optimization improves visibility on search engines.
  • Analytics Integration: Integrating analytics (such as Google Analytics) allows me to track user interactions on the site, helping me understand user behavior, popular content, and areas that may need improvement. This data is invaluable for optimizing the user experience over time.