How to Sync Your Blog to WeChat Official Account Using Node.js and Redis
This tutorial explains how to use the WeChat material management API, Redis caching, and Node.js middleware to obtain access tokens, upload and replace images, inline CSS, and finally create draft articles for automatic publishing on a WeChat public account.
This article, originally from zhiyi's personal blog, demonstrates how to synchronize articles from other platforms to a WeChat public account using the WeChat material management API, focusing on backend integration for front‑end developers.
Overall Idea
WeChat provides a material management API that requires authentication via an access token. Images must be uploaded through the WeChat image upload API and replaced in the article, and external CSS must be converted to inline styles because WeChat only supports inline CSS.
Steps
Use the public account appid and secret to obtain an access token.
Upload all images in the article with the WeChat image upload API and replace the URLs (authenticated with the access token).
Convert external CSS to inline styles.
Call the WeChat material management API to sync the article (authenticated with the access token).
Getting the Access Token
Fetching the token is straightforward; the appid and secret are sent to the token endpoint. Because the endpoint has rate limits, the token is cached in Redis. The following Koa middleware registers a Redis client and provides Promise‑based get, set, expire, and del methods:
module.exports = (redisConfig) => async (ctx, next) => {
const client = redis.createClient(redisConfig.port, redisConfig.host);
client.auth(redisConfig.password);
ctx.redis = {
get: (key) => new Promise((resolve, reject) => {
client.get(key, (err, result) => {
if (err) reject(err);
else resolve(result);
});
}),
set: (key, value) => new Promise((resolve, reject) => {
client.set(key, value, (err, result) => {
if (err) reject(err);
else resolve(result);
});
}),
expire: (key, expire) => new Promise((resolve, reject) => {
client.expire(key, expire, (err, result) => {
if (err) reject(err);
else resolve(result);
});
}),
del: (key) => new Promise((resolve, reject) => {
client.del(key, (err, result) => {
if (err) reject(err);
else resolve(result);
});
})
};
await next();
};When retrieving the token, the code first checks Redis; if missing, it requests the token from WeChat, stores it in Redis with the provided expiration, and returns it.
module.exports = async (ctx) => {
try {
let accessToken = await ctx.redis.get('wechatAccessToken');
if (!accessToken) {
const res = await axios.get(`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${wechatConfig.appid}&secret=${wechatConfig.secret}`);
if (res?.status !== 200) {
throw new Error('get access token error');
}
accessToken = res?.data?.access_token;
const expiresIn = res?.data?.expires_in;
if (!accessToken) {
throw new Error('get access token error');
} else {
await ctx.redis.set('wechatAccessToken', accessToken);
await ctx.redis.expire('wechatAccessToken', expiresIn);
}
}
return Promise.resolve(accessToken);
} catch (err) {
return Promise.reject(err);
}
};Uploading and Replacing Images
The article content is an HTML string. All <img> tags are extracted with a regular expression, each src URL is downloaded as a stream, uploaded via the WeChat image upload API, and the returned WeChat URL replaces the original URL.
const images = parsedContent.match(/<img.*?(?:>|\/>) /gi);
if (images) {
for (const image of images) {
const src = image.match(/src=['"]?([^'\"]*)['"]?/i);
const url = src?.[1];
// process URL
}
}Cover images are handled similarly but use the material add_material endpoint, which returns a media_id used when creating the draft.
const coverImageStream = (await axios.get(coverImage, { responseType: 'stream' })).data;
const coverImageRes = await request({
url: `https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=${accessToken}&type=image`,
method: 'POST',
formData: { media: coverImageStream },
});
const coverImageId = JSON.parse(coverImageRes).media_id;Converting External CSS to Inline
The npm package juice can inline CSS with a single call:
parsedContent = juice(`
<style>${cssString}</style>
<div>${parsedContent}</div>
`);Creating the Draft Material
Finally, the prepared content, cover image ID, and other metadata are sent to the draft/add endpoint:
await axios.post(`https://api.weixin.qq.com/cgi-bin/draft/add?access_token=${accessToken}`, {
articles: [{
title: 'Your Article Title',
thumb_media_id: coverImageId,
author: 'Article Author',
digest: 'Article Summary',
content: parsedContent,
content_source_url: 'Original Link',
need_open_comment: 1,
show_cover_pic: 0
}]
});Unresolved Issues
WeChat does not provide an API to declare an article as original; this must be done manually on the desktop client.
The API only supports using the central part of the cover image, while the web console allows custom cropping.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
