Chase Mao's blog

Blog Building From Zero (3) Blog Server: Nodejs, React and Golang

2024-06-13

Preface

In last part Blog Building From Zero (2) Basic Server: HTTP, HTTPS, and Nginx, we build a https server with nginx. In this part, our goal is to build a full stack blog server.

Design

As mentioned before, there are lots of blog server application. With these apps, we just need to add articles in it. But what we want to do, is to develop a blog server application ourselves.

As for how to design blog server, first question is to chooze monolithic architecture or frontend-backend decoupling architecture. With monolithic architecture, it may be simpler to develop but when adding new feature it may be more complexe. So I chooze to frontend-backend decoupling architecture.

As for how to build frontend and backend, I chooze Nodejs, React, and Typescript for frontend and Golang for backend. Nodejs, React, and Typescript are very popular and with the component it is easy to build a website. And Golang is very efficient to build a backend application.

So the whole architecture will be like:

frontend side

Client —>(send page request)—-> Nodejs

Client <—(receive React page)<— Nodejs

backend side

Client —>(send data request)—-> Nodejs —>(proxy request)—> Golang

Client <—(receive data)<——— Nodejs <—(receive data)<—- Golang

A bit of more explain:

  • Nodejs is the server application that receive request from client first
  • When client is requst a page, Nodejs will return a html that is build by React
  • To render that page, client may need some data. For example, client may need article list. The requset action is defined in the react html.
  • When client browse that page, it will send a fetch data request to Nodejs, and Nodejs will proxy that request to real backend developed by Golang.

Git

To build a new application, of course we need git to maintain our code.

To use github (or other repository platform), we need sign up new account and install git in our vm, and set up proper git config to push and pull code with github.

To focus on development part, we will leave how to use github and git to Google and Chatgpt.

The frontend and backend github repository is listed below:

Nodejs and React

First we need to install Node

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# installs nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# download and install Node.js (you may need to restart the terminal)
nvm install 22

# verifies the right Node.js version is in the environment
node -v # should print `v22.3.0`

# verifies the right NPM version is in the environment
npm -v # should print `10.8.1`

And then we will init Node, Typescript, and necessary lib.

1
2
3
4
5
npm init -y

npx tsc --init

npm install express body-parser typescript node-fetch

Init React, Typescript, and necessary lib.

1
2
3
4
5
6
7
npx create-react-app blog

cd blog

npm install axios

npm install typescript react-markdown @types/node @types/react @types/react-dom @types/jest @types/react-syntax-highlighter

After init, we will develop the server.ts file for Nodejs like this.

Then we need to set up tsconfig.json for Nodejs. To proper import lib, module and target should be set to esnext, and exclude set to ["blog/*"]. In case when convert Typescript file into js, it will look into React project.

And then we can convert server.ts to server.js, and run it.

1
2
3
4
5
6
npx tsc

node server.js

# test it in vm
curl localhost:5000

There are better way to test. I would recommand use VSCode as IDE to connect to remote VM and use port forward to browse it in local browser.

After build Nodejs server, we need to build the website page. And before that, we should design how the page should look like. There are many fancy ways to put on amazing effect on the website. I prefer to make it simple and easy to read. So I designed the main page layout into a header, and two column under header. Left column is article list, and right column is some introdution and feature may be added in the future. And detail page (example) layout is simple header and the detail.

To be more specific, I developed a App component as a Router and it define the header too like below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <a href="/" style={{ textDecoration: 'none', color: 'inherit' }}>
          <h1>{header}</h1>
        </a>
      </header>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<ArticalList />} />
          <Route path="/article/:title" element={<ArticleDetail />} />
          <Route path="/404" element={<NotFound />} />
          <Route path="*" element={<RedirectToNotFound />} />
        </Routes>
      </BrowserRouter>
    </div>
  );
}

There are two main component in it, ArticalList and ArticleDetail. We can look into the detail in this file and the css is in this file.

A tricky part worth mentioning is that how to render markdown and code in it. I use react-markdown to render markdown and react-syntax-highlighter to render code in markdown like below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<ReactMarkdown
    components={{
    code({ node, inline, className, children, ...props }: any) {
        const match = /language-(\w+)/.exec(className || '');

        return !inline && match ? (
        <SyntaxHighlighter
            style={prism}
            PreTag="div"
            language={match[1]}
            {...props}
        >
            {String(children).replace(/\n$/, '')}
        </SyntaxHighlighter>
        ) : (
        <code
            className={className}
            style={{
                background: 'rgb(245, 242, 240)',
                padding: '5px',
            }}
            {...props}>
            {children}
        </code>
        );
    },
    }}
>
    {article.content}
</ReactMarkdown>

Dont forget to set jsx to react in tsconfig.json.

Finish developed React project, run npm run build to build it.

I run into some problems in it. Because the vm memory is few (2 GB), when build react it always taken up too much memory causing system to crash.

Before I upgraded vm to 8 GB, I use cgroup to control memory usage of node.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# install cgroup tool
sudo apt-get install cgroup-tools

# add below into ~/.bashrc or other shell script
# it will limit cpu max 80% and memroy max 300M, modify accordingly
sudo cgcreate -a ubuntu:ubuntu -t ubuntu:ubuntu -g cpu,memory:node_group
sudo cgset -r cpu.cfs_quota_us=80000 node_group
sudo cgset -r memory.limit_in_bytes=300M node_group

# make modify effect
source ~/.bashrc

# run command with cgroup, can also set some alias
cgexec -g cpu,memory:node_group runsomething

It help me from crashing system, which can lead to failure of build command. So eventully I upgraded vm for better use.

Aftere React is prepared, we can run Nodejs server.

1
2
3
4
node server.js

# test it
curl localhost:5000

Once Nodejs server run normally, we can configurate it with nginx.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
sudo vim /etc/nginx/nginx.conf

# add in http{}
server {
   listen 443 ssl;
   listen [::]:443 ssl;
   server_name your_domain.com; # your domain here
   ssl_certificate       /usr/local/etc/v2ray/server.crt; 
   ssl_certificate_key   /usr/local/etc/v2ray/server.key;
   ssl_session_timeout 1d;
   ssl_session_cache shared:MozSSL:10m;
   ssl_session_tickets off;
   ssl_protocols         TLSv1.2 TLSv1.3;
   ssl_ciphers           ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
   ssl_prefer_server_ciphers off;
    
    location / {
       proxy_redirect off;
       proxy_pass http://localhost:5000; # Nodejs server run in localhost
       proxy_http_version 1.1;
       proxy_set_header Upgrade $http_upgrade;
       proxy_set_header Connection "upgrade";
       proxy_set_header Host $host;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   }
}

# reload nginx config
sudo systemctl reload nginx

With nginx set up, when we browse the blog with https, we should be able to open react page just designed.

Golang

We have developed the frontend, and now it comes to the backend.

The backend will provide article list, detail and image.

About how to organize these data, I have several thoughts, mysql, elasticsearch, or local file. Using database makes it easy to do query and maintain data. But it will be more troublesome to configurate and adding new article. So I decide to use local file pulling from github in the beginning. In this way I can push new article to github, and set a crontab to fetch data from github into local. So the update is nearly real time.

1
2
3
4
5
# add crontab
crontab -e

# git account should be set up
* * * * * cd /home/ubuntu/workspace/blogarticle && git pull

And then I develop API to fetch list and detail of article with Gin. Gin is a http web framework and very easy to use. The router and handler is developed.

There is a problem of how to show image in article. In markdown there are some three ways to show image.

  • Upload image into some place, such as github, or cloud provider, got its url and put it in markdown. Some website may prevent others from read its image, and when putting image in cloud provider, we have to put image (cloud provider) and article (github) in different place, and if the free browsing is used up, we will be charged.
  • Convert image into base64 data, and put base64 data in markdown. The biggest problem is base64 data is very very long, and make markdown so ugly.
  • Maintain the image url in own server, and put the url in markdown. I chooze this way, because I can put article and image in the same git repository. And the backend will provide article and image together by local file pulled from github.

After decided how to design backend, lets review basic Go usage, download and install go following its guide.

1
2
3
4
5
# init go module
go mod init github_repository_uri

# run
go run .

After development, we can run the golang, prepare some articles and test it.

1
curl localhost:6666/api/v1/article/list

It should return article list. If it does, we can test it in blog.

Deploy

If above steps works fine, we should be able to browse the blog. And we can push new article to git to update the blog.

One problem left is that the node and golang application will not start itself if system reboot or some error happen. So we can use set up a service to make sure it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# add a service called blog
sudo vim /etc/systemd/system/blog.service

# file content
[Unit]
Description=Run Blog

[Service]
ExecStart=/home/ubuntu/workspace/bin/run_blog.sh
Restart=always
User=ubuntu
Group=ubuntu

[Install]
WantedBy=multi-user.target

# reload systemd
sudo systemctl daemon-reload

# make sure start automatically at boot
sudo systemctl enable blog

# start service
sudo systemctl start blog

# check status
systemctl status your-service-name

# lookup log when necessary
sudo journalctl -u blog

Congratulations! A blog server is up and running in the vm.

Summary

In this part We introduced how to build a frontend-backend decoupling architecture blog server with Nodejs, React, Typescript and Golang. And we deploy it in vm. With these set up, we can push new article to git, and it will update blog in nearly realtime.

What is not so perfect is that the server is just deployed in one vm, if that vm is down, then the whole blog is down. So in next part Blog Building From Zero (4) Distrubted Deploy: Docker and Kubernetes, we will introduce how to use docker and kubernetes to deploy blog server in multi vms.