I just had to take a markdown service out of my API

I just had to take a markdown service out of my API

Daily short news for you
  • I got an email from Windsurf today - they said I'm an active user so they're giving me early access to Windsurf Next. What's that?

    It's just the next version of Windsurf, they're improving a lot of things in it. After trying it out for a day, I think the Vietnamese support has gotten better and the suggestions are more accurate now. I'm not sure yet, I'll have to use it more to see, that's my initial impression.

    Oh, I'm still loyal to the free version 😆.

    » Read more
  • One thing I find a bit confusing is that Postman requires an internet connection to function, everyone. This has probably been the case for several years now; back then, many people complained about it, but it’s still the same, still needing the internet to work.

    They must think that IT folks always have internet access, which is why they added the requirement for a network connection just to be safe. Indeed, I am rarely or never affected since I always have internet while working. However, I believe many others find this issue extremely frustrating. Proof of that is that after this incident, quite a few open-source projects have been created to replace Postman.

    Among them, I see the most notable one is hoppscotch. The interface looks quite similar to Postman, but it seems "nicer" 😁

    » Read more
  • Have you ever tried reading the Google Analytics (GA) API documentation? The first time I read it, I was completely lost, not understanding what they were presenting, what the next steps should be... in general, the way it was presented was so convoluted that I couldn’t read it all at once to get immediate results.

    However, if you interact with it more, you'll find that it follows a certain standard. This means that the next time you read it, you'll know what to read first and what to read next, and then move on to the steps to get results, which makes you a bit more proficient. While I was fumbling around with the GA API, I discovered this website that helps me quickly test queries for results, and then I just need to copy the query and put it into the API. Quick and easy! 😅

    GA4 Query Explorer

    » Read more

The Issue

This past weekend, I sat down to research solutions to improve the SEO performance of 2coffee. I realized that there was one method I hadn't applied yet, which is CLS. In simple terms, CLS measures the amount of layout change that occurs on a web page. For example, imagine a user is reading your website and suddenly the text jumps down to make room for an image that is still loading. This action causes some discomfort for the user, as the sudden layout change while reading is frustrating. Now, what if it's not just the text but a button the user was about to click? It would feel like being "pranked" in a way.

Development

My blog usually has one or more images in each post, with one main image representing the entire post. Therefore, the situation where the text is loaded first and then the image suddenly jumps in can happen. This is especially noticeable for mobile users with slow internet speeds or weak devices.

low CLS

There is a way to solve this issue, which is to specify the height for the images. Initially, I thought that setting width: 100%; height: auto would be enough, but I was wrong. Without explicitly specifying the height, the browser cannot reserve space for the image. In other words, if you want to reserve space, you need to specify the height. This is when the issues started to occur.

  • First, it's not possible to set a fixed height for all images because they have different dimensions. Doing so could distort the appearance of other images.
  • The solution I thought of was to explicitly specify the height for each image in the markdown editor. Then, by combining it with the CSS property aspect-ratio: attr(width) / attr(height), I could resize the images comfortably. It seemed like everything was solved until another issue arose.

My posts are stored in markdown format, which allows me to write articles without dealing with complex HTML code. The benefit of markdown is that it helps keep the articles "clean" and organized. However, the markdown-to-HTML library I'm using does not support adding attributes to the <img> tag.

I tried to find another library, but it seems that none of them could fulfill my requirements. I attempted to read the source code of the current library for a solution, but it seemed beyond my reach. Maybe it's because I didn't understand it well enough, or maybe I didn't want to spend too much time on it. That's when I thought, if I were using Node.js, there would surely be numerous libraries that could handle this problem (For those who don't know, I'm using Golang to write the API).

Indeed, after a few searches, I found showdown, which supports adding attributes to the <img> tag. At this point, I have to say that the Golang library has no chance against Javascript. Partly because the JS community is very large, so almost every library exists.

This led to the decision to create a separate service dedicated to parsing markdown using Javascript and showdown. But no matter what, I had to ensure that the service's calling speed was "somewhat" comparable to a native function in Go. Therefore, I decided to implement gRPC and use internal method calls.

Approach

For those who don't know, gRPC is a framework that allows you to call functions from another service with high speed and reliability. Similar to REST, gRPC uses HTTP to transport information, but it uses HTTP/2.

There are several reasons why I chose gRPC for this task. Firstly, I wanted the service to be only called internally (with no intention of publishing it). Secondly, I needed the speed benefits that gRPC provides. And thirdly, I wanted a clear distinction between internal service calls and published service calls, something that gRPC excels at.

Implementing gRPC tends to be more complex than REST. You have to write more code and the implementation of gRPC calls can be more intricate. However, the reliability and speed trade-offs make it worthwhile.

The name of this service is markdown, and it is written in Node.js. In markdown, there is only one very simple .proto file with one function:

syntax = "proto3";
package hicoffee.markdown;

service Markdown {
  rpc ConvertMarkdownToHTML (ConvertMarkdownToHTMLRequest) returns (ConvertMarkdownToHTMLReply) {}
}

message ConvertMarkdownToHTMLRequest {
  string markdown = 1;
}

message ConvertMarkdownToHTMLReply {
  string html = 1;
}

The implementation in Node.js is also very straightforward:

const PROTO_PATH = __dirname + '/protos/markdown.proto';

require('dotenv').config();
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const { ConvertMarkdownToHTML } = require('./handlers/markdown');

const packageDefinition = protoLoader.loadSync(
  PROTO_PATH,  
  {
    keepCase: true,  
    longs: String,  
    enums: String,  
    defaults: true,  
    oneofs: true
  });
const markdown_proto = grpc.loadPackageDefinition(packageDefinition).hicoffee.markdown;

function convertMarkdownToHTML(call, callback) {
  callback(null, { html: ConvertMarkdownToHTML(call.request.markdown) });
}

function main() {
  const server = new grpc.Server();
  server.addService(markdown_proto.Markdown.service, { ConvertMarkdownToHTML: convertMarkdownToHTML });
  server.bindAsync(process.env.ADDRESS, grpc.ServerCredentials.createInsecure(), () => {
    server.start();
    console.log(`Server running at ${process.env.ADDRESS}`);
  });
}

main();

There is only one main function, convertMarkdownToHTML, which simply uses the showdown library to convert markdown to HTML and returns the result.

In Go, implementing the client to make calls to markdown took a bit more time. Basically, it still relies on the .proto file to call the function, with the added step of building the .proto file into two *.pb.go files before being able to use it.

$ protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative protos/markdown.proto

For more detailed instructions on how to install and build the Protocol Buffer Compiler, please refer to Protocol Buffer Compiler Installation.

Finally, to check if markdown is working, I used bloomrpc. Bloomrpc is a GUI client that allows for quick and easy gRPC calls.

Testing the markdown function

Since the calls are internal, I didn't implement additional client authentication. If you plan to implement public gRPC services, consider looking into security.

Finally, I deployed everything to the server, and they work as expected.

Conclusion

There's no problem that can't be solved. The solution depends on your experience, starting from simple to complex. If I only knew Go and wasn't aware of gRPC... things might have gone in a different direction. I hope this article provides you with another approach to problem solving. There's no one-size-fits-all solution, only the most appropriate one based on the circumstances you choose.

Premium
Hello

Me & the desire to "play with words"

Have you tried writing? And then failed or not satisfied? At 2coffee.dev we have had a hard time with writing. Don't be discouraged, because now we have a way to help you. Click to become a member now!

Have you tried writing? And then failed or not satisfied? At 2coffee.dev we have had a hard time with writing. Don't be discouraged, because now we have a way to help you. Click to become a member now!

View all

Subscribe to receive new article notifications

or
* The summary newsletter is sent every 1-2 weeks, cancel anytime.

Comments (0)

Leave a comment...