It’s time to practice working with APIs. We can work with APIs on the frontend or backend, so we will practice both!
Let’s show off our github account to people! We will add a list of our github repositories to our personal site so that people know how good at coding we are.
We will be using the List User Repos endpoint of the github api
Take a few minutes to see if you can get the API to work for you in Postman. You should be able to make a GET
request, inputing your own github username, and see a list of your repos.
For me, the request looks like this:
GET https://api.github.com/users/abeaclark/repos
Let’s look at the response. We get an array with objects inside. Each object represents one of our repos. You can look through the attributes to see all of the information that is available to you regarding that repo. Github even gives you other urls you can request to see expanded information.
Let’s add this to our mobile site. To do so, we will need to use javascript. So, let’s create another file called main.js
in the root directory of our project.
touch main.js
This is where we will put the javascript we are using.
To make things more fun, let’s require people to press a button in order to see our repositories (instead of just displaying them)
This means, that we will need to add a few things to our html file:
In our JS file, we will need the following:
Take a few minutes to try to do this on your own. It’s ok if you get stumped for a little while (Even I did for a bit creating this demo).
Here is how I would approach it:
index.html:
Abe's site
Hello everyone!
main.js:
jQuery(document).ready(function($) {
function getRepos() {
$.get("https://api.github.com/users/abeaclark/repos")
.then(function(data) {
var repoContainer = $('#repoContainer')
displayRepos(repoContainer, data)
})
}
function displayRepos(container, repos) {
$.each(repos, function(index, repo){
container.append('' + repo.name + ' ')
})
}
var button = $('#showRepos')
button.on( 'click', getRepos)
});
A few notes:
jQuery(document).ready(function($) { ... })
. This makes sure that your javascript doesn’t run until the browser has finished rendering the DOM. If you don’t do this, you’ll notice some very annoying and hard to debug errorsid
so that I can find them easily in my javascriptgetRepos
I use the javascript promise that jquery $.get()
returns to wait to display the repos until after they have returned from the API callIf you are feeling excited by this activity, try adding some of these:
.catch
function that will show an error on the screen when the API request fails.CORS (Cross Origin Resource Sharing) is a security measure that servers use. You can read up on it, but the details are a little hard to swallow at first. What you need to know is that often API requests will fail if you are serving your file directly from the file system. That is what we were doing in the example above. And example of this can be seen if you open your index.html
from above and reveal the developer console (<COMMAND>
<OPTION>
i
). We are going to make a call to another API that enforces this standard.
Paste this code into your web console (To get an inspirational quote from this API:
$.get('http://quotesondesign.com/wp-json/posts')
You should see an error come back that looks like this:
XMLHttpRequest cannot load http://quotesondesign.com/wp-json/posts. The 'Access-Control-Allow-Origin' header has a value 'http://localhost:8000' that is not equal to the supplied origin. Origin 'null' is therefore not allowed access.
This error cost me nearly 15hrs at the first hackathon I attended. One common reason for this error is that you are not serving your content from a web server, you are just opening the file directly from your computer. You can tell this because if you look in your URL bar, you will see something like this:
file:///Users//Desktop/test/index.html
When you serve files using a web server, it will look more like this:
localhost:8000
But, we really want to include and be able to test this API. How can we? Well, we need to server our files on a server.
Luckily there is a simple way to do this using a built-in python function. In your terminal, navigate into the base directory for your site (where you have index.html
). Then run this function:
python -m SimpleHTTPServer
You will see something like this:
Serving HTTP on 0.0.0.0 port 8000 ...
That means that your site is being served on a simple web server on port 8000.
To see your site, visit http://localhost:8000
Now, let’s try the API command from before:
$.get('http://quotesondesign.com/wp-json/posts')
It should work! If you change the code like this, you should see the response printed:
$.get('http://quotesondesign.com/wp-json/posts').then(function(data){ console.log(data)})
You may sometimes see this error for other reasons (such as leaving out an API key), but this is always a good thing to check first!
If we can access APIs on the frontend, why would we want to do it on the backend. There are a few reasons, but one of the most important is when you are required to use secret keys to access the API. If we include the keys on the front end, they will be accessible to anyone. This is a problem. Let’s say you are using an API that costs you $0.01/request. If someone gets ahold of your key, they could make unlimited requests on your account and rack up quite a bill!
Let’s practice this. We’ll modify our service so that people can text the joke to a friend!
Here is an overview of how we’ll do this:
View
in Django) that will accept the phone number and call the Twilio API to send a textLet’s add another url and controller to the app. We’ll send the phone number and the text of the joke to this endpoint. Then, this endpoint will trigger the Twilio API to send a text message. Then it will let our frontend know that everything worked (Or send back error text if it didn’t).
Create a new controller inside your views.py
file. You can add it underneath your Home
class. You will also need to import HttpResponse
:
from django.http import HttpResponse
from django.views import View
from django.shortcuts import render
from django.http import HttpResponse
import pyjokes
class Home(View):
... Your Home Code here ...
class TextJoke(View):
def post(self, request):
print(request.POST)
return HttpResponse("Success")
Then we need to add this to our urls.py
file. Add the import and a line to the urlpatterns
.
from django.conf.urls import url
from django.contrib import admin
from jokes.views import Home, TextJoke
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', Home.as_view()),
url(r'^send-text', TextJoke.as_view()),
]
Great! Let’s visit our new endpoint to check it out. Run your server and visit http://localhost:8000/send-test.
Interesting…I saw the same exact page as our based route. There was a funny joke, but not what I was hoping to see.
Why isn’t it working correctly? Well, it has to do with the order that our urls are listed in. Since the urls use regex, Django starts testing the requested url from the top down in the url list. In our case, it looks at r'^'
first (which corresponds to nothing after the base url). The problem with this is that EVERY url will match this pattern, because there isn’t really any pattern to match. The way around this is the change the order of our urls. Bump the home page url down to the bottom of the list:
from django.conf.urls import url
from django.contrib import admin
from jokes.views import Home, TextJoke
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^send-text', TextJoke.as_view()),
url(r'^', Home.as_view()),
]
Now if you refresh the page, what happens? I got a white screen. What were you expecting? I was expecting the text “Success”. Hmmm… Why isn’t it working?
Go back to your views.py
file and compare the code for the Home
view with the code for the TextJoke
view.
One important difference is the name of the function inside the class. In the Home
view, the function name is get
, but in the TextJoke
view, the function name is post
. You probably guessed, this determines how you are able to access the endpoint. If we want to get the response we are looking for, we need to send a post request to that endpoint.
By default the web browser performs a get
request when you type in a url. To do a post request, let’s use Postman.
Do a POST request to http://localhost:8000/send-text
You likely got another error (Do you hate me yet???). Mine has a lot of text output, but this is the main part:
CSRF verification failed. Request aborted.
You are seeing this message because this site requires a CSRF cookie when submitting forms. This cookie is required for security reasons, to ensure that your browser is not being hijacked by third parties.
Feel free to research about CSRF verification. In short, it is a security measure to make sure that it is the frontend of your site talking to the backend and not someone trying to pretend they are your frontend. This is important if you are serving your html from you backend (That’s what we are doing in this example).
What’s the solution? Well, Let’s move on to the next part of our current project, and we’ll see how it is handled.
Let’s edit our template to add a form that will call our API endpoint.
Check out this funny joke: {{ joke }}
Text yourself this joke:
Take a look at the code above:
input
s are wrapped in a form
element. This automatically sends the data to the url in the action
attributeaction
attribute is directed to the endpoint we created and then method
makes sure it is a POST request{% csrf_token %}
takes care of the security issue we ran into earlier (Django makes this easy)label
and input
for the phone number make it clear to the user where to type their numberinput
for the joke is hidden, and the value is set to {{ joke }}
. This means that the current joke will be sent to our endpoint as wellsubmit
. This means it is will be a button that submits the form and calls the action
methodRefresh your browser on the home page. you should see the form. Give it a try!
You will be taken to the new page at /send-text/
that shows success!
Next, sign up on Twilio for an account. Each account comes with test account credentials, so you shouldn’t have to pay any money up front.
Go to Phone Numbers > Tools > Test Credentials
(Direct Link) and take note of these two test keys; You will need them to access the API:
Install the twilio python sdk
pip install twilio
Then, add code to send a text message to your views.py
. I broke it out into a helper method called sendTextMessage
.
from django.http import HttpResponse
from django.views import View
from django.shortcuts import render
from django.http import HttpResponse
import pyjokes
from twilio.rest import Client
# Your Account SID
account_sid = "AC23ca8d59856f5a920b63242af3b8b9e2"
# Your Test Auth Token
auth_token = "ce26ed0fb2dc5513e85d6c3ba716c1d9"
# Instantiate the twilio SDK with your values
client = Client(account_sid, auth_token)
# Helper method for sending text messages
def sendTextMessage(phone_number, body):
return client.messages.create(
to=phone_number,
from_='+15005550006',
body=body)
class Home(View):
def get(self, request):
context = {
'joke': pyjokes.get_joke()
}
return render(request, "home.html", context)
class TextJoke(View):
def post(self, request):
print(request.POST)
phone_number = request.POST.get('phone-number')
joke = request.POST.get('joke')
try:
sendTextMessage(phone_number, joke)
except:
return HttpResponse("There was an error sending the message")
return HttpResponse("Success")
Above, you will also noticed that I wrapped the sendTextMessage
helper method in a Try/Except block. A Try Except block is a common pattern found in programming languages. You use it when you have a part of your code that may throw an error if something goes wrong. In this case, we wrap the sendTextMessage
helper method in a Try block because if there is an error, we want to display a different message to the user. The except block determines what happens if the try block finds an error.
In this case, we want to return the text “There was an error sending the message” if there is an error calling twilio, but return “Success” if not. A great place for a Try/Except block.
Now, if you run this code and submit a valid phone number, you should see “Success” on the new screen. If you input an invalid phone number, you should see the error message.
One thing you probably noticed is that in either case, you don’t actually get the text message. This is OK! Unfortunately, Twilio actually doesn’t let you send a real text message with test credentials. For that you need to sign up for a real account and pay a little money. Your choice if you would like to! But, we can still practice as if the text message is being sent, because if the twilio code doesn’t return an error, we know it worked!
Congrats! You’ve got a form that calls an API.