Tweet html-node as an image using ReactJS and NodeJS
Sorin Chis
Posted on April 14, 2020
This article was born while building Codelify. Giving users the opportunity to share a code snippet is a great way to show your ideas to other people in your network.
In this article we will learn how to create an image from any html-node of your site. Once we have the image we will post it in a tweet along with the text and links that we want.
This article will be divided in two parts:
- NodeJs server with express (backend)
- ReactJs part (frontend)
Part one
Before we can start coding, we need to create an account on Twitter and get a developer account approved by the Twitter team.
For this you will need to create a twitter account and then login into
twitter developer website
- a good example about how to create an account can be found here
Once your developer account application has been approved, we can move on with creating a Twitter developer application and get access to the tokens we can use in our code.
Create a Twitter Developer Application
To create an application, go to the apps section of your developer account and click on the Create an app button.
After filling out the form with all information required, hit create button to create your application.
Get Your Access Tokens
Go to the page for your newly created application and open the Keys and tokens tab.This page lists the keys and tokens for your application. In our code, these will be used for authentication when we make requests to the Twitter API.
Configure Node.js Server
Let's create a directory for our code:
$ mkdir server-tweet
And then cd into your directory:
$ cd server-tweet
We will have to run npm init for creating package.json file that will hold the configuration for our code.
Next we will create two files. The first one is for starting the server, named index.js and the other one will be used for tweeter endpoints.
$ touch index.js
$ touch server.js
Install Dependencies
Next, we need to add the NPM package dependencies that will be used in our project. To make things easier we'll just add everything we need right now.
- npm install express
- is a minimal and flexible Node.js web application framework that provides a robust set of features to develop web and mobile applications. It facilitates the rapid development of Node based Web applications.
- npm install cors
- the easiest way to get CORS working in Express is by using the cors npm module
- npm install dotenv
- helps with storing configuration in the environment separate from code
- npm install ba64
- a tiny npm module for saving Base64 encoded images that are part of data URLs to your file system
- npm install twitter
- an asynchronous client library for the Twitter REST and Streaming API's.
The twitter npm package will do a lot of the grunt work in making requests and interacting with the Twitter API.
Configure Environment Variables
Create an .env file in your project root, add the code below. Make sure you add your access keys found in your Twitter Developer Dashboard.
CONSUMER_KEY="YOUR_CONSUMER_KEY"
CONSUMER_SECRET="YOUR_CONSUMER_SECRET"
ACCESS_TOKEN_KEY="YOUR_ACCESS_TOKEN_KEY"
ACCESS_TOKEN_SECRET="YOUR_ACCESS_TOKEN_SECRET"
Open index.js and add these code:
require("dotenv").config();
const dotenv = require("dotenv");
const server = require("./server.js");
const PORT = 9000;
server.get("/", (req, res) => {
res.send("<h1>Yeee! Server working</h1>");
});
server.listen(PORT, () => {
console.log(`\n*** Server Running on http://localhost:${PORT} ***\n`);
});
We use the dotenv.config() function, which reads our .env file, parses its contents and assigns the value to the global process.env object.
As you can notice we still need to create our server.js file.
Add the following code in server.js file:
const express = require("express");
const Twitter = require("twitter");
const ba64 = require("ba64");
const fs = require("fs");
const server = express();
const cors = require("cors");
const client = new Twitter({
consumer_key: process.env.CONSUMER_KEY,
consumer_secret: process.env.CONSUMER_SECRET,
access_token_key: process.env.ACCESS_TOKEN_KEY,
access_token_secret: process.env.ACCESS_TOKEN_SECRET
});
server.use(cors());
server.use(express.json());
module.exports = server;
We create a new Twitter client object with all the access keys from our .env file. We'll use that client object to make requests to the Twitter API throughout the rest of this article.
Now it will be a good time to check your server functionality so far. You can start your server by running npm run start
, and go to localhost:9000. If everything its working normally you should see the message: Yeee! Server working.
After you check that everything it's working so far we can implement our server.post() request to get dataURL
from the client(react part).
// POST method route
server.post('/imagetotweet', (req, res)=> {
//our dataURL will be in req
const { dataURL } = req.body;
console.log(dataURL)
})
Once we will have the dataURL
we will need to save the image to file with the help of ba64 package
.
server.post("/imagetotweet", async (req, res) => {
const { dataUrl, shareId } = req.body;
// console.log(dataUrl);
ba64.writeImage("myimage", dataUrl, (err) => {
if (err) {
console.log("Write image error", err);
}
console.log("Image saved successfully");
});
});
Now the image will be saved in the root file of our application.
Then, we read the image using the fs module. This result will represent the data that we will attach to media/upload twitter endpoint
server.post("/imagetotweet", async (req, res) => {
const { dataUrl } = req.body;
ba64.writeImage("myimage", dataUrl, (err) => {
if (err) {
console.log("Write image error", err);
}
console.log("Image saved successfully");
fs.readFile("myimage.png", (err, data) => {
if (err) {
console.log("Read file err", err);
}
try {
//twitter api endpoints call : media/upload
} catch (error) {
res.status(500).json({ error: error.message });
}
});
});
});
After that, we requested the Twitter API to upload the image with the client.post method. The request goes to the "media/upload" endpoint and the only data we add to the request is the image.
Once the image is successfully uploaded, Twitter will return a media_id_string value. Then we create a variable called status that holds both the text and media_id for the new twitter post.
server.post("/imagetotweet", async (req, res) => {
const { dataUrl } = req.body;
// console.log(dataUrl);
deleteImage();
ba64.writeImage("myimage", dataUrl, (err) => {
if (err) {
console.log("Write image error", err);
}
console.log("Image saved successfully");
fs.readFile("myimage.png", (err, data) => {
if (err) {
console.log("Read file err", err);
}
try {
client.post(
"media/upload",
{
media: data,
},
function (error, media, response) {
if (error) {
console.log("MEDIA UPLOAD", error);
} else {
const status = {
status: "Just made a tweet",
media_ids: media.media_id_string,
};
// twiter endpint call : statuses/update
}
}
);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
});
});
The last thing we have to do is to use the client.post to make a statuses/update request to the twitter API. The only data we need to include in the request is the status that we've created previously.In case of success we will send the response (image id) with a 200 status code to the client. Then we handle the error and log the tweet object response.
This is how the final version of our request will look like:
server.post("/imagetotweet", async (req, res) => {
const { dataUrl } = req.body;
// console.log(dataUrl);
deleteImage();
ba64.writeImage("myimage", dataUrl, (err) => {
if (err) {
console.log("Write image error", err);
}
console.log("Image saved successfully");
fs.readFile("myimage.png", (err, data) => {
if (err) {
console.log("Read file err", err);
}
try {
client.post(
"media/upload",
{
media: data,
},
function (error, media, response) {
if (error) {
console.log("MEDIA UPLOAD", error);
} else {
const status = {
status: "Just made a tweet",
media_ids: media.media_id_string,
};
client.post("statuses/update", status, function (
error,
response
) {
if (error) {
console.log(error);
} else {
res.status(200).json({
message: response.entities.media[0].display_url,
});
// console.log("Display URL: ", response.entities.media[0].display_url);
}
});
}
}
);
} catch (error) {
res.status(500).json({ error: error.message });
}
deleteImage();
});
});
});
statuses/update endpoint will return a displayURL string that will actually be our image id. Once we put this id in the tweet content, twitter will recognize the id and it will show the specific image (displayURL will look something like this:
pic.twitter.com/AxDW8sCzdZ
)
Notice that I've added an extra function at the end deleteImage in which after all of the request are done we will delete the image from the root.
const deleteImage = () => {
const path = "myimage.png";
if (fs.existsSync(path)) {
//file exists
fs.unlink(path, (err) => {
if (err) {
console.error(err);
return;
}
//file removed
});
}
};
Now your endpoint it's ready to be used on the client side. Next we will implement the ReactJs part in which we will make a post request with dataURL
and the server will return us the displayURL
:)
Part two
If you don't already have an ReactJS application you can easily create one with create-react-app.
Once you have your react app running you will have to install 2 packages:
dom-to-image package which will turn arbitrary DOM node into (PNG or JPEG) image, optionally you can create SVG too.
axios package for making HTTP requests from the browser to the server.
First step you have to import these two packages into your component:
import domtoimage from "dom-to-image";
import axios from "axios";
and then add the content that you will want to copy and publish it on twitter. After this you will need to assign an id or if you are comfortable you can use refs in react and attach it to a DOM node
<div id="content-to-be-copied">
<h1>My fancy section</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua
</p>
</div>
Next step it's to create a button and add a click listener to it for handling share functionality.
<button onClick={handleShare}>
Publish on Twitter
</button>
In handleShare function we will take the node that we will want to copy.
let node = document.getElementById("content-to-be-copied");
From dom-to-image package we will use .toPng option to turn the DOM node into an PNG image - this will return a promise, which are fulfilled with corresponding data URLs (get a PNG image base64-encoded data URL).
domtoimage
.toPng(node)
.then(dataUrl => {
console.log("Data URL: ",dataUrl)
})
.catch(err => console.log(err));
Once we will have dataUrl
for the image we are half way done with the frontend part. Next we will have to send the data to the server with a HTTP request using axios.
Axios is a promise based HTTP client for the browser and Node.js. Axios makes it easy to send asynchronous HTTP requests to REST endpoints and perform CRUD operations.For sending data to the server we will use axios.post() method and attach the dataURL to the body.
axios.post('http://localhost:9000/imagetotweet', { dataUrl });
Once an HTTP request is made, axios returns a promise that is either fulfilled or rejected, depending on the response from the backend service. To handle the result, you can use the .then() and .catch() methods.
In case of the request is successfully we will receive the displayURL
id from the server (this will be our image).
axios.post("http://localhost:9000/imagetotweet",{dataUrl: dataUrl})
.then(res => {
// console.log(res.data.message);
})
.catch(err => console.log(err, "Error trying to tweet"))
Handling the request
We are almost over with the react part - after we get a successfully message we will need to prepare the tweet content.
Twitter url will receive 4 variables
- url (optional) your website address
- via(optional) your tweeter handler
- title: the response from the server res.data.message (image id)
- hashtags (optional) add any hashtags you want to appear in your tweet
const url = "www.codelify.dev";
const via = "codelify_dev"; //your twitter handler
const title = res.data.message;
const hashtags = "reactJS,tweet,codelify";
const twitterURL =`https://twitter.com/shareurl=${url}&text=${title}
&via=${via}&hashtags=${hashtags}`;
window.open(twitterUrl,"twitter");
//openTwitterUrl(twitterURL); //optional for creating a custom window
Optionally you can create a custom function to send the twitter url. In this function we can set the size of tweeter window
function openTwitterUrl(twitterUrl) {
const width = 575;
const height = 400;
const left = (window.outerWidth - width) / 2;
const top = (window.outerHeight - height) / 2;
const opts =
`status=1,width=${width},height=${height},top=${top},left=${left}`;
window.open(twitterUrl, "twitter", opts);
}
After we do all of this steps our handleShare function will look something like this:
const handleShare = () => {
let node = document.getElementById(`content-to-be-copied`);
domtoimage
.toPng(node)
.then(dataUrl => {
axios
.post(
"https://backend-url.com",
{
dataUrl: dataUrl,
}
)
.then(res => {
const url = "www.codelify.dev";
const via = "codelify_dev";
const title = res.data.message;
const hashtags = "reactJS,tweet";
const twitterURL =
`https://twitter.com/shareurl=${url}&text=${title}&via=${via}
&hashtags=${hashtags}`;
window.open(twitterUrl,"twitter");
//openTwitterUrl(twitterURL); //optional
})
.catch(err => console.log(err, "Error trying to tweet"))
})
.catch(err => console.log(err));
};
After hitting the endpoint from the react app a twitter window will pop up with the image id and the message
After tweet the post should look something like this
This is basically how we implement the sharing functionality in Codelify
Codelify give developers a central place to easily Store, Manage, Retrieve and Share code snippets.
The code for the backend can be found in this Github repository for this article.
Conclusion
As this is my first article the code and content could be improve in many ways, I would appreciate any feedback, good or bad, in order to improve the next articles.
Thanks for reading and happy coding! :)
Posted on April 14, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.