Today we are going to build a Twitter Post Scheduler application. Let’s call it Twittler.
Tools
Before we start, make sure you have
- Node and NPM on your computer. You can download both at nodejs.org.
- A code editor. I prefer VSCode. You can download it at code.visualstudio.com.
Twittler High-Level Architecture
Here’s a quick overview of how our application will work.
Client part:
- Users write a tweet, choose a time when they want to schedule it, and click send.
- A tweet goes into the database and is stored there.
Server part:
- NodeJS calls the database every minute to retrieve all the tweets that need to be sent out that minute.
- Then NodeJS posts these tweets on Twitter.
Twittler Tech Stack
To build Twittler, we will use:
- NodeJS (on the server)
- ReactJS (on the client)
- Fauna (as our database)
But before we dig into the code, first, we need to create a Twitter developer account to get access to Twitter API to start posting on Twitter.
Twitter Developer Account
Here’s how to create a Twitter developer account:
- Go to https://developer.twitter.com/en/apply-for-access
-
Click the “Apply for a developer account” button
-
Choose why you want to get access to Twitter API (I chose to explore)
-
Complete all of the following steps, answer questions and submit your application.
Wait until the Twitter team finishes reviewing your application and gives you access to the API (It may not take more than a couple of days)
-
After the Twitter team will approve your application, go to your dashboard and create a new project.
-
Then copy and save a Bearer Token. We will need it to access the Twitter API.
Now, let’s set up our database.
Fauna
For the database, we will use Fauna. It is a serverless database that gives you ubiquitous, low latency access to app data without sacrificing data correctness.
- Log in or sign up at your Fauna account here.
-
Click on “Create database.”
-
Choose name and region
-
Click on “New collection.”
-
Choose collection name and click “Save.”
We just created a new database called "twittler" and our collection "tweets" where we're going to store our tweets.
What does “collection” mean for Fauna?
Collections are sets of data records, called documents. In our case, a set of tweets. If you are familiar with relational databases, collections are analogous to tables in them.
Now we need to generate API keys and put them in our application so that our server can access the database to retrieve tweets from it. Here’s how to do it:
-
Go to the “Security” tab and click on “New Key.”
-
Type a key name and click on “Save”
-
Our API key was generated.
Save the key somewhere. We will need it later to access Fauna from our application.
And the last thing we should do is create an index.
Indexes in Fauna allow us to retrieve documents by attributes other than their Reference. They act as a lookup table that improves the performance of finding documents. Instead of reading every document to find the one(s) you are interested in, you query an index to find those documents.We will use the Index to get all tweets from a specific date range.
To create it, go to “Indexes” tab:
And create a new index, “tweetsByDate”
Click the “Save” button, and let’s start to code our client.
Client
To create our client application, we will use ReactJS, and we can quickly install it using create-react-app.
Open your terminal and install create-react-app by using the following command:
npx create-react-app twittler
Then go to the created folder and initialize our project:
cd twittler && npm i
Now, let’s install Fauna package that we will use in our application to get access to the database from our client:
npm i fauna
And also, we need to add the Fauna secret key that we have created in the Fauna security tab. To do this crate .env.local
file in the root folder of our project and put your Fauna secret key there:
// .env.local
REACT_APP_FAUNADB_SECRET=your-secret-key
The last thing we need to add is TailwindCSS. It’s a utility-oriented CSS framework to help us quickly design and build our application without writing any CSS. To do it go to public/index.html and add a link to TailwindCSS css file.
// public/index.html
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
<title>Twittler</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
This is not the best way to add TailwindCSS to applications, but we remove extra installation steps required when installing "correctly" with npm. In real-world applications, we would install Tailwind using npm.
Now that everything is set up, time to write some code. Let’s start with creating our UI.
Open src/App.js and add this code:
// src/App.js
import React, {useCallback, useState} from 'react'
import Fauna from 'Fauna'
const currentDate = new Date().toISOString().substr(0, 10)
const FaunaClient = new Fauna.Client({
secret: process.env.REACT_APP_FAUNADB_SECRET,
})
const q = Fauna.query
function App() {
const [tweet, setTweet] = useState('')
const [date, setDate] = useState(currentDate)
const [time, setTime] = useState(
new Date().getHours() + ':' + new Date().getMinutes()
)
const sendTweet = useCallback(
async (event) => {
event.preventDefault()
console.log(new Date(`${date} ${time}`).getTime())
console.log(new Date(`${date} ${time}`))
try {
FaunaClient.query(
q.Create(q.Collection('tweets'), {
data: {
tweet,
date: new Date(`${date} ${time}`).getTime(),
},
})
)
setTweet('')
} catch (error) {
console.log(error)
}
},
[date, time, tweet]
)
return (
<form
onSubmit={sendTweet}
className="flex flex-col max-w-lg m-auto min-h-screen justify-center"
>
<h2 className="mb-6 text-center text-3xl font-extrabold text-gray-900">
Your Tweet
</h2>
<textarea
required
maxLength="280"
rows="5"
className="mb-6 focus:ring-indigo-500 focus:border-indigo-500 border-2 w-full p-4 sm:text-sm border-gray-300 rounded-md"
placeholder="I don't understand pineapple pizza"
value={tweet}
onChange={(event) => setTweet(event.target.value)}
/>
<div className="flex items-center mb-8">
<input
required
type="date"
min={currentDate}
value={date}
onChange={(event) => setDate(event.target.value)}
className="focus:ring-indigo-500 focus:border-indigo-500 border-2 w-full p-4 sm:text-sm border-gray-300 rounded-md mx-4"
/>
<input
required
type="time"
value={time}
onChange={(event) => setTime(event.target.value)}
className="focus:ring-indigo-500 focus:border-indigo-500 border-2 w-full p-4 sm:text-sm border-gray-300 rounded-md mx-4"
/>
</div>
<button
type="submit"
className="flex justify-center py-4 px-4 border border-transparent font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Schedule Tweet
</button>
</form>
)
}
export default App
So what’s happening here?
Using Fauna.Client
, we create a Fauna client function with a secret key as a parameter to access the Fauna API.
const FaunaClient = new Fauna.Client({
secret: process.env.REACT_APP_FAUNADB_SECRET,
})
Using FaunaClient.query
, we send a request to Fauna to create a new document with tweet
and date
parameters.
FaunaClient.query(
q.Create(q.Collection('tweets'), {
data: {
tweet,
date: new Date(`${date} ${time}`).getTime(),
},
})
)
To test this code yourself, open the terminal and type the following command:
npm start
And try to write a tweet like “I don’t understand pineapple pizza” (tell the truth), pick a date, time and click on the “Schedule Tweet” button. If the request succeeds, then you have created a new document in the collection tweets
.
You can view the result in your Fauna dashboard:
The client part is done, now let's write our server.
Server
Our server will live in the server
folder in the root folder of our application. Create the folder and place a single file, index.js,
out of which we'll run our server.
To create our server, we will use a popular NodeJS framework ExpressJS (we could only use NodeJS for our application, but with ExpressJS, we will create API routes and extend our functionality application in the future).
To install ExpressJS, run the following command in the root folder of your project in the terminal:
npm i express
After, we need to install the cron-job package so that our server can request Fauna every minute. To do it run the following command in your terminal:
npm i node-cron
Also, we need to install the dotenv package. It will load environment variables from a .env.local
file into process.env.
This way, we can access the REACT_APP_FAUNADB_SECRET
variable from our server code.
To do it, run the following command in your terminal:
npm i dotenv
The last package we need to install is the twitter-api-v2. It will help us to post tweets on Twitter.
npm i twitter-api-v2
Before we start coding, we need to add the Twitter API bearer token we saved in the previous steps. To do this, open the file .env.local
and add your Twitter bearer token under the REACT_APP_FAUNADB_SECRET
:
// .env.local
REACT_APP_FAUNADB_SECRET=your-secret-key
TWITTER_BEARER_TOKEN=your-twitter-bearer-token
Now, let’s write the server itself. Open server/index.js
and add this server code:
// server/index.js
const express = require('express')
const cron = require('node-cron')
const Fauna = require('Fauna')
const {TwitterApi} = require('twitter-api-v2')
const twitterClient = new TwitterApi(process.env.TWITTER_BEARER_TOKEN)
const q = Fauna.query
const faunaClient = new Fauna.Client({
secret: process.env.REACT_APP_FAUNADB_SECRET,
})
// run every minute
cron.schedule('* * * * *', async () => {
const now = new Date()
now.setSeconds(0)
now.setMilliseconds(0)
try {
// get all tweets from Now - 1 minute to Now
const {data} = await faunaClient.query(
q.Map(
q.Paginate(q.Match(q.Index('tweetsByDate'), now.getTime())),
q.Lambda(['date', 'ref'], q.Get(q.Var('ref')))
)
)
// post all tweets from date range on twitter
data.forEach(async ({data: {tweet}}) => {
try {
console.log(tweet)
await twitterClient.v1.tweet(tweet)
} catch (error) {
console.log(error)
}
})
} catch (error) {
console.log(error)
}
})
const app = express()
app.listen(3001, async () => {
console.log(`Server listening on ${3001}`)
})
Let’s see what is interesting happening here.
The cron.schedule
calls every minute the function responsible for publishing tweets to Twitter.
Using faunaClient,
we get all tweets in the range of current time and a minute earlier.
const {data} = await faunaClient.query(
q.Map(
q.Paginate(
q.Range(
q.Match(q.Index('tweetsByDate')),
minuteAgo.toISOString(),
now.toISOString()
)
),
q.Lambda(['date', 'ref'], q.Get(q.Var('ref')))
)
)
And using twitterClient.v1.tweet(tweet)
we post them on Twitter.
Our application is ready. Now, let's test everything.
Run Application
First, create a script in package.json
file that will start our web server when we run the command npm start server
in our console:
// package.json
...
"scripts": {
"server": "node -r dotenv/config ./server/index.js dotenv_config_path=./.env.local",
...
}
Then open a terminal and run npm start server
in one window to start our web server, and in another window, run npm start
to start our client.
The Twitter Post Scheduler app is ready!
You can find a repository with the final example here.
In the part 2 we will deploy our application to Vercel.
Top comments (0)