SETTING UP AN API: PART 3 (DATABASE)

  

Some commands will require storing information and then recalling it later. How would one create a database to do that? 

What's the goal?

Part 3 in the building API series will walk through adding a simple database to our server to save information and recall it later. As we work through this example, I'm going to explain how I approach adding code whenever I start a new custom widget/overlay and API call.

As with any project, it all starts with an idea:




WARNING: 

Servers should always be private and are not intended to be readable by users!

We are using the free service provided by replit which is great for projects like this but it is not protected when using the free version. For example you can see my full code if you go to my mine: https://replit.com/@pjonp/chatbot-api (and fork it).

This project will include the first "secret" that we need to hide from other people. We need to try our best to prevent them from seeing the link to our server.

I'll come back to this later. For now we want to make sure that any command that uses our server only says ${customapi} on your commands page, https://streamelements.com/YOURNAME/commands


Setting up the database

Before we get into making the route for this project we need to set up a database. This will store the multiTwitch link so the server can read it later and send it to chat.

In the shell copy and paste (right click paste): npm i @replit/database and hit Enter


This will install the server code the next time we do a restart.

Our code needs to initialize the database and start it. Use this code at the top of the index.js server file right after we bring in the Express server:

  1. const Database = require('@replit/database'), //repl database
      db = new Database();



Adding our multiTwitch route

While it is possible (and better practice) to make 2 separate routes, one to "save" (PUT) the information and a second one to retrieve (GET) it, for this example we will only be using a single GET route to do both. Just remember as you build your own projects that you can (and should) split the routes up between saving and reading.

The first thing I do when creating a new route is to add the basic function and create an outline for what I want to achieve. Let's start with this code for our third route and then fill in the objectives:

  1. //***Route 3: MultiTwitch link. 
    app.get('/api/multitwitch', (req, res) => { //.../api/multitwitch?squad=...&ismod=...
    
    //[]check if the sender is a Mod
    //[]if user is not a mod; then get the link from the database and "return" that to chat
    //[]if user IS a mod AND there is 1 or less words after then do same as above 
    //[]if user IS a mod AND there are 2+ words AND first word starts with @, make a new link
    
    //[] Get link from database function
    //[] Save link to database function
    
    //[]send message
    
    });
    //***



This approach helps focus better on the end goal and breaks it down into smaller steps. The first step being to define the route: //.../api/multitwitch?squad=...&ismod=...
We know we want to check 2 pieces of information (Queries); "squad" to make the link from and then check if the command was sent by a user that "ismod".

  1. //***Route 3: MultiTwitch link. 
    app.get('/api/multitwitch', (req, res) => { //.../api/multitwitch?squad=...&ismod=...
    const squad = req.query.squad || ''; //set the words after the !multitwitch command as a string OR set it to an empty string ''
    const isMod = req.query.isMod === 'true'; //check if the link has ismod as true. this is only sent from MOD command
    ...

We set up two variables to match our expected queries: setting squad to match our input, expecting a mod to say !multitwitch user1 user2 user3 and grabbing those usernames to add to our link. (!multitwitch will be removed when we set up the commands in SE). If no usernames are included we set the squad as an empty string "". Then we do a mod check. (mod information is also passed later during command setup).

Then start working through the code objectives. 


The first one is pretty straight forward. We can just check if the sender is NOT a mod. Only mods can edit the link so this will always return the link from the database from non-mods.

We can move onto the else for formatting the message if it was sent by a mod and fill in more of the outline:


CODE / OVER-EXPLANATION WARNING:
I fill in all possible outcomes first as the functions at the bottom. The sendMsg function at the bottom will send our response back to chat. There are two possible messages; either get the link that is in the database or put a new one.

The getLink function will read our database, get the existing link and then send it to chat.
Or the saveLink which will put a new link before sending to chat.

Once these are defined, I can fill in the end of line 33, sending all non-mod message straight to getLink()

Otherwise (else) the command is sent by a mod.
I split the squad into each word to determine the squad members that are intended  to be in the multiTwitch link.

Since the expected command is !multitwitch user1 user2 user3 and the command !multitwtich was already removed from the command (we'll get to that later), the String "user1 user2 user3" is converted into any Array ['user1','user2','user3'] by using the split() function and separating each one by every space. (' ') on line 36.

Now on line 38, I can check the length. If the mod sent 1 or less users, then I want the response to be the link that is already saved. This prevents a moderator from accidentally clearing the link or trying to create a multiTwitch link with only one username. If you wanted to add the option for your mods to remove or delete the existing multiTwitch link, this would be a good place to check for something such as !multiwitch remove 😀
At this point, the logic has narrowed it down that the moderator must be trying to update the link so saveLink is called on line 40 and I pass along the username Array

The basic logic for the handling the command is done now, but I still need some more code in the saveLink function to convert ['user1','user2','user3'] into a multitwitch link.


I want to prevent the same issue we ran into with the @user targets from part 2 so I create a new variable on line 50. squadArray was passed in and I map it to create a new Array while running the regex on each element. (Similar to a "forEach" on username).

Line 53 is the reverse of the split() from a few lines above. This time I join() the usernames, but instead of having spaces between the usernames, I want /'s. At this point joinUsernames is "user1/user2/user3".

Finally; I take that String and create a multiTwich link: multitwitch.tv/user1/user2/user3


Using the database

Basic logic [x].
Now let's use this database thing.

Our third route should look like this at this point:
  1. //***Route 3: MultiTwitch link. 
    app.get('/api/multitwitch', (req, res) => { //.../api/multitwitch?squad=...&ismod=...
    const squadString = req.query.squad || ''; //set the words after the !multitwitch command as a string OR set it to an empty string ''
    const isMod = req.query.isMod === 'true'; //check if the link has ismod as true. this is only sent from MOD command
    //[x]check if the sender is a Mod
    //[x]if user is not a mod; then get the link from the database and "return" that to chat
    if(isMod === false) getLink()//sender is not a mod: CALL GET LINK FUNCTION
    else { //sender is a mod (used the mod command)
      //[x] split Squad string by spaces to get the words (convert to array)
      const squadArray = squadString.split(' '); //split the string by each space into an Array
      //[x]if user IS a mod AND there is 1 or less words after then do same as above
      if(squadArray.length <= 1) getLink()//CALL GET LINK FUNCTION
      //[x]if user IS a mod AND there are 2+ words make a new link
      else saveLink(squadArray)//CALL SAVE FUNCTION and pass along the "squadArray"
    };
  2. function getLink() {
      //[] Get link from database function
    
    
    };
    
    function saveLink(squadArray) {
      //squadArray = ['@a','@b','@c']
      //[x] remove the @ from usernames if mentions
      const removeAts  = squadArray.map(i => i.replace(/[^\w]/g, '')); //look at each user and use the replace from previous Example to remove the @'s'
      //squadArray = ['a','b','c']
      //[x] convert array back into string - multitwitch.tv/ link
      const joinUsernames = removeAts.join('/'); //join the usernames with a / between
      //joinUsernames = 'a/b/c'
      const multiTwichLink = `multitwitch.tv/${joinUsernames}`;
      //multiTwichLink = `multitwitch.tv/a/b/c`  
      //[] Save link to database function
      
    
    };
    
    function sendMsg(message) {
      //[x]send message
      res.send(message);
    };
    
    });
    //***

Let's set up the saveLink() first.

Using the db.set(KEY, VALUE).then(() => {... format, add this at the end of the function:

  1. db.set('multiTwichLink', multiTwichLink).then(() => { //save the string //[x]send message sendMsg(multiTwichLink) });


If you have used the SE KV Store for widgets before this might look similar. If not, spoiler, it's the same format StreamElements uses for widget databases 😉

We took our data, the multiTwitch link, and saved it as a Key named "multiTwichLink".

Now all we need to do is reverse the process and read the same key using db.get(KEY).then(value => {...

Drop this into the empty getLink function:

  1. db.get("multiTwichLink").then(value => {
          if(!value) throw Error(); //verify that there is multiTwitch link
          sendMsg(value);
        })
      //[x] error check if there is nothing in the database 
        .catch( _=>{
          sendMsg('There is no multiTwitch link set up.');
        })
    


We get the value of the muliTwtichLink KEY, then make sure it exists. If there is NO value we force an error. That error is handled by the catch at the end. When there is a value in the database (our link) we send that back for the bot to say in chat. If there is an error, the 'catch' message "no link" is sent instead.


That's it. Our route is complete:

  1. //***Route 3: MultiTwitch link. 
    app.get('/api/multitwitch', (req, res) => { //.../api/multitwitch?squad=...&ismod=...
    const squadString = req.query.squad || ''; //set the words after the !multitwitch command as a string OR set it to an empty string ''
    const isMod = req.query.isMod === 'true'; //check if the link has ismod as true. this is only sent from MOD command
    //[x]check if the sender is a Mod
    //[x]if user is not a mod; then get the link from the database and "return" that to chat
    if(isMod === false) getLink()//sender is not a mod: CALL GET LINK FUNCTION
    else { //sender is a mod (used the mod command)
      //[x] split Squad string by spaces to get the words (convert to array)
      const squadArray = squadString.split(' '); //split the string by each space into an Array
      //[x]if user IS a mod AND there is 1 or less words after then do same as above
      if(squadArray.length <= 1) getLink()//CALL GET LINK FUNCTION
      //[x]if user IS a mod AND there are 2+ words make a new link
      else saveLink(squadArray)//CALL SAVE FUNCTION and pass along the "squadArray"
    };
  2. function getLink() {
      //[x] Get link from database function
      db.get("multiTwichLink").then(value => {
          if(!value) throw Error(); //verify that there is multiTwitch link
          sendMsg(value);
        })
      //[x] error check if there is nothing in the database 
        .catch( _=>{
          sendMsg('There is no multiTwitch link set up.');
        })
    };
    
    function saveLink(squadArray) {
      //squadArray = ['@a','@b','@c']
      //[x] remove the @ from usernames if mentions
      const removeAts  = squadArray.map(i => i.replace(/[^\w]/g, '')); //look at each user and use the replace from previous Example to remove the @'s'
      //squadArray = ['a','b','c']
      //[x] convert array back into string - multitwitch.tv/ link
      const joinUsernames = removeAts.join('/'); //join the usernames with a / between
      //joinUsernames = 'a/b/c'
      const multiTwichLink = `multitwitch.tv/${joinUsernames}`;
      //multiTwichLink = `multitwitch.tv/a/b/c`  
      //[x] Save link to database function
      db.set('multiTwichLink', multiTwichLink).then(() => { //save the string
          //[x]send message
          sendMsg(multiTwichLink);
      });
    };
    
    function sendMsg(message) {
      //[x]send message
      res.send(message);
    };
    
    });
    //***

Hit the STOP button on the server then run it again. (Note: this will take a minute as it installs the database package we added at the start).


Setting Up The Commands

Fire up your StreamElements dashboard, we need to add two commands and do some testing.

We need a command for everybody to use to get the muliTwitch link and a command that only moderators can use to set the link.

(Replace your server and name with yours)
First the everybody GET command. (user level: everyone): $(customapi.https://bouncyartisticportablesoftware.pjonp.repl.co/api/multitwitch)


Second the moderator "PUT" (user level: mod): $(customapi.https://BouncyArtisticPortablesoftware.pjonp.repl.co/api/multitwitch?squad=${1:|null}&isMod=true)

The ${1:} is to grab all of the inputs after the the command, so the command "!setmultitwitch" is not sent. 


For those taking notes; you'll notice that the isMod isn't really needed (only the mod command will send the "squad" info, it's always null in the everyone command). A goal for this example was to show handling more than query in a single route.

You will also notice that both of these links have the same .../api/multitwitch base link. We used queries for the moderator command to pass in the information to set the link and define the moderator. Generally, you would want to set up 2 different routes with the same base; one to put the information and one to get the information. Since we are making ChatBot commands, they will always be GET requests

You could split split the routes up and move all of the "mod" command logic it's route such as .../api/putmultitwitch

Final note: this is the first 'unsecure' route. Anyone that knows about your link "https://BouncyArtisticPortablesoftware.pjonp.repl.co/api/multitwitch?squad=${1:|null}&isMod=true" can set your link 😬.

You can set my multiTwitch to anything you want.... (seriously, test it 😁)
https://chatbot-api.pjonp.repl.co/api/multitwitch?squad=this link is not secure&isMod=true
Then see the result:
https://chatbot-api.pjonp.repl.co/api/multitwitch

Always be aware that the API server link (just the chatbot-api.pjonp.repl.co part) will allow users to see see your server source code and endpoints! Revisit the the warning at the top of this post and double check that none of your commands include your server link and NEVER post your server link for others to view if you have a route that you don't want any user to be able to use.

[x] Create an API server
[x] Send information to server from chat commands
[x] Set up a database
[ ]  Call other APIs with our API ....

Comments