11 min read
"How a Young Man Developed a WeChat Mini Program in 45 Days Alone"











Previously, I mainly talked about how I came up with the idea for this community mini-program and how it went live. Interested friends can refer back to the previous two articles. This time, I will discuss how it was technically implemented.
How I Turned an Idea into a Product in One Month
Technology Stack
The front end is mainly implemented based on Taro + Typescript + dva framework, while the back end is primarily based on Ruby on Rails.
Here, I want to explain why I chose this technology stack. There is a detailed analysis of technology selection in the book "Run, Programmer," which I will share in my reading notes series later.
The main consideration for technology selection is that if it’s a project that needs to be quickly prototyped, one should choose a lighter language with extensive community component support, and also something familiar to oneself.
Front End
Why use Taro? This is mainly determined by my past experiences. I have been engaged in technical research and development for 13 years. Although I spent less time coding in the last few years, I still consider myself to be working on the front lines. When React Native first emerged a few years ago, I quickly got the hang of it due to my experience in JS development and Android development. My previous experience with hybrid apps and dynamic template rendering for mobile at Ele.me helped me quickly understand the design concepts and principles of React.
So this time, I smoothly used the Taro front-end development framework, which is primarily based on React. Although uni-app is well-known, it requires learning Vue from scratch. I found it relatively quick to recall the previous Redux setup. There’s no need to argue about right or wrong; if it allows you to comfortably and quickly complete your work, then it’s right.
Speaking of Typescript, I remember that the biggest problem I encountered when writing JS was the lack of prompts. For full-stack developers like us, switching contexts too quickly can lead to spending a lot of time looking up methods. To save on code volume, I even went through a phase of writing CoffeeScript. Although my project code wasn’t very standardized, it did solve a significant portion of my problems.
At this point, some might say that I wasn’t using it correctly. It’s similar to Vim and Emacs; many people find them too convenient. I even spent time learning Vim shortcuts. However, in the end, I still couldn’t get used to it. I prefer using `command + x` for deletion in VS Code, a habit formed over more than a decade that isn’t easy to change.
Again, there’s no right or wrong; it’s just about what you’re comfortable with.
Back End
In fact, I have written quite a bit in Java over the past few years, but I won’t elaborate on that here. The current project hasn’t undergone significant stress testing, but if the user base grows too large to handle, then we can discuss the sweet troubles that arise. I took some time to research Ruby on Rails again and found that it has added many features, such as Job, ActiveStorage, Webpack, Turbolinks, etc., which greatly enhance support for full-stack development.
Most importantly, Ruby on Rails has excellent support for testing. My personal habit is that if the code isn’t covered by tests, I can easily introduce bugs, especially if I haven’t worked on that part of the business for a long time or am not familiar with it.
Mini Program
Project Structure
First, here’s a basic structure diagram for everyone to understand.
The general process is as follows; I won’t elaborate too much here. Interested friends can look into the dva framework and Redux themselves.
Next, I will discuss some issues I encountered during the development of the community mini-program and my thoughts and methods for solving them. If you have better solutions, feel free to reach out to me for discussion.
Login
To be honest, login is a very annoying task. There was a certain probability of encountering **Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt**. Later, I found the solution in the official documentation:
So I adjusted it to:
Textarea Penetration Issue
There’s a feature in the community mini-program where users can comment on someone’s post, which leads to a problem where the underlying input box appears above the overlay. You can refer to the image below; I didn’t take a screenshot at the time. No matter how you set the z-index, it doesn’t work.
The reason for this issue is that the Textarea is a native component, which has a higher stacking order than web components, so I resolved it this way.
However, this approach led to another problem: due to focus issues, the cache might be empty, and if the user directly clicks the send button, we need to check if there’s content in the submission. If there is, we submit it directly; if not, we use the cached content. If both are empty, we provide a non-empty prompt.
Additionally, some classmates mentioned the issue of the input box being covered by the keyboard, similar to the image below.
The solution here is relatively straightforward; refer to the official documentation for this property.
dva-model-extend and Model Layer
Often, while implementing logic, I encounter frustrating issues where several pages have similar logic but with slight differences, and this page is coupled with some other module logic. The most common example is shown below.
The various business components in the image involve community, posts, users, likes, comment logic, and some logic specific to the page. So how should we divide them? You can refer to the image below.
Business Base Class
This is mainly responsible for implementing common logic, such as obtaining user-related baseUser, post-related basePost, etc. However, they do not have their own namespace and cannot be called directly; they can only exist as base classes.
Business Class
This is responsible for the actual invocation of common business logic, such as user class userModel, post class postModel, etc. The benefit of this approach is that any page can call them.
For example, if I want to obtain user information for each person on the post list page, I can directly dispatch a user type. Its logic is relatively standard.
Interface Class
This class provides specific services for each page. For instance, in the image above, if I have customized the return content for posts to store a separate state, I can inherit from the base business class and modify its reducers' action implementation. Each interface class will also be associated with a page.
Data Class
Why is the data class separate? What’s the use? In the front end, we often encounter a significant issue where, for example, Page A uses user information, and Page B also uses user information. If we follow the traditional approach of maintaining user information separately for each page and updating it through an event bus, the problem is that a lot of redundant data is stored in memory, and events are flying everywhere, making it unclear which part triggered the data change on that page.
Therefore, we need a data layer to maintain this, somewhat like a front-end in-memory relational database. The interface retrieves a bunch of IDs, and only when it needs to display does it query the specific data from the data layer and then render the interface.
Data Rendering
As mentioned above, we used to render data directly and then modify it through the event bus. This can lead to a very troublesome issue.
Still using the community mini-program as an example, if I want to delete a comment from a post, what should I do?
Initially, this left me very frustrated, as there was no way to continue, and it was also prone to issues, significantly increasing the workload for testing.
Then I discovered the front-end tool normalizr, which can help us accomplish the tasks described in the data layer above. The specific process can be referenced in the image below.
To save space, I won’t elaborate too much here. Since all pages are referenced, once the data changes, all pages will follow suit. Moreover, handling data only requires managing one layer of associations, without needing to deal with multi-layer data structures, as it helps flatten the data.
Summary
I have shared the basic structure of the project, logical layering, and some thoughts on data processing above, which I believe should be helpful for everyone in developing mini-programs.
Back End
Having discussed the basic architecture of the front end, let’s talk about the back end. For early-stage projects, as long as the front end handles the data and logic architecture well, the rest are mostly interface issues, CSS-related manual work, and continuous multi-device compatibility tuning.
The back end involves more aspects, such as monitoring, data processing, microservices, disaster recovery, etc. I have encountered some of these over the years, but for a new project, these things are not the most important.
The most crucial aspect of implementing a new project is how to iterate faster and provide new interfaces. The reputation of CRUD developers is not just talk.
From the early WebService to the current microservices, the concepts have been continuously updated, but the essence has not changed much. The goal has always been to reduce risk. Back when I was at Xiaomishu, I started working with SOAP and WSDL, but for innovative businesses, technology should not be a barrier to efficiency.
When I hear about creating a service specifically for a single table, I feel like it's microservices for the sake of microservices. When I want to address an issue, I need to make changes from the gateway all the way down to the final layer of services. What could be resolved in a few minutes ends up taking an entire day just for debugging.
Everyone has different opinions, and there is no right or wrong in technology. Faced with different backgrounds, everyone makes different choices. I have seen many companies with excellent technical architecture that died due to slow iterations. I have also seen many companies with severe internal conflicts that still thrive.
I digressed a bit earlier; let's return to the topic. Startups mainly need to handle a few key things. Of course, if you have other viewpoints, feel free to discuss. Some parts of this reference the source code from ruby-china, for which I am very grateful.
**Interface and Response Template**
How do we understand the interface and response template? Simply put, it means your interface can return data.
This time, I did not use the Grape Gem but directly used Rails API and Jbuilder for rendering templates.
First, I created a parent rendering template.
This means that it will always return the three keys: code, message, and data, where data can be either an Array or an Object.
Then, in `application_controller.rb`, I specified the parent layout.
Next, in the application directory, I created a generic template for each entity, such as `_user.json.jbuilder`, determining whether it is a simple or complex object based on parameters.
For example, in the user list, there might be three main values: nick_name, id, and avatar. When you view a specific person's profile, you might need to know additional information, such as age, gender, cellphone, etc.
The corresponding interface rendering can refer to the following.
Basically, your interface response ends here. One additional note: if you are using Rails API, you need to include the following reference for Jbuilder.
**Error Capture and Alerts**
This can be divided into several parts.
**Error Codes**
You can choose to create a dedicated class to maintain error codes.
**Error Messages**
I won't elaborate on this; I directly created an `api.zh-CN.yml` for I18n to maintain error messages.
**Error Classes**
Here, I will briefly explain that they are mainly divided into three categories.
Since it does not include complex data objects, errors can be rendered directly as JSON.
**Permission Verification**
I used `cancancan`; you can check GitHub for specific usage. Here, I will explain how it is applied. It is mainly divided into two parts: the first part is the permission judgment for backend business logic.
**Backend Business Permissions**
You can refer to the above, which can be divided into the following situations.
Here, I will mention the `roles_for_members` permission, which will have more detailed divisions according to various businesses, and the business permissions will have interface-level permission condition judgments.
**Frontend Business Permissions**
For example, in the frontend, each person's status is different. As the group owner, I can delete posts made by some users in my group, but I cannot delete posts made by others. So how is this done?
During frontend rendering, each object will carry a permission table indicating what actions I can perform on this object.
For instance, when I open someone's information page, it will return whether I can disable or report this person. Similarly, there are permissions for whether posts can be deleted, pinned, etc.
**Deployment**
There are many ways to deploy. In the early years, before containerization technology became popular, everyone needed to configure the environment and then deploy using Capistrano or Jenkins, etc.
However, for projects that are just starting, it is unnecessary to set up such services. If your server only has 2GB, it is not cost-effective to mess around with Jenkins. So here, I mainly use
**docker + docker-compose** for deployment.
You can refer to the diagram for easier understanding.
First is the Dockerfile.
Then is `docker-compose.production.yml`.
Finally, our deployment script `deploy-production.sh`.
Of course, the above method has many issues, such as indicating success even when deployment fails. However, at this point, it is also easy to go to the machine to resolve issues or change configurations for image rollback.
**Testing**
Testing mainly uses RSpec; you can learn more about it on your own. I'm running out of steam here. Since it's an independent project, no one will help you test; you need to ensure the robustness of your code. If anyone is interested, I will write a dedicated article about testing strategies later. Just make sure to cover all your interfaces and core classes with test cases. For my part, I put the code on GitHub and directly connect it to Travis CI to ensure that the code on the master branch passes tests before release.
**Finally**
I welcome everyone to comment and discuss, learning from each other. Recently, many friends have approached me to talk about the background and ideas of projects, but a few words cannot clarify everything. I will write an article about my understanding of communities and some views on business models, so feel free to continue following me.
Focusing on internet entrepreneurship sharing, independent developer. Same name across the internet: Lu Canwei.
专注互联网创业分享,独立开发者。全网同名:卢灿伟