One place for hosting & domains

      Shortener

      How To Make a URL Shortener with Flask and SQLite


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Flask is a framework for building web applications using Python and SQLite is a database engine that you can use with Python to store application data.

      In this tutorial, you will build a URL shortener, a service that takes any URL and generates a shorter, more readable version like bit.ly.

      Hashids is a library that generates a short unique ID from integers. For example, you can use it to convert a number like 12 to a unique string like 1XcId. You will use Hashids to generate unique strings for URL IDs.

      You can use unique strings to generate IDs for videos on a video-sharing site or IDs for images on a service to upload images. This unique string gives you unpredictable IDs; therefore, if a user can access an image at your_domain/image/J32Fr, they can’t predict the location of other images. This is not possible if you use integer IDs in a URL shortener—for example, your_domain/image/33 would allow users to predict the location of other images. Unpredictable URLs add a form of privacy to your service because they prevent users from working out different URLs shortened by other users.

      You will use Flask, SQLite, and the Hashids library to build your URL shortener. Your application will allow users to enter a URL and generate a shorter version, in addition to a statistics page where users can view the number of times a URL has been clicked. You’ll use the Bootstrap toolkit to style your application.

      Prerequisites

      Step 1 — Setting Up Dependencies

      In this step, you will activate your Python environment and install Flask and the Hashids library using the pip package installer. Then you’ll create the database you will use to store URLs.

      First, activate your programming environment if you haven’t already:

      Once you have activated your programming environment, install Flask and the Hashids library using the following command:

      • pip install flask hashids

      Then create a database schema file called schema.sql, containing SQL commands to create a urls table. Open a file called schema.sql inside your flask_shortener directory:

      Type the following SQL commands inside this file:

      flask_shortener/schema.sql

      DROP TABLE IF EXISTS urls;
      
      CREATE TABLE urls (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
          original_url TEXT NOT NULL,
          clicks INTEGER NOT NULL DEFAULT 0
      );
      

      In the schema file, you first delete the urls table if it already exists. This avoids the possibility of another table named urls existing, which might result in confusing behavior; for example, if it has different columns. Note that this will delete all of the existing data whenever the schema file executes.

      You then create the table with the following columns:

      • id: The ID of the URL, this will be a unique integer value for each URL entry. You will use it to get the original URL from a hash string.
      • created: The date the URL was shortened.
      • original_url: The original long URL to which you will redirect users.
      • clicks: The number of times a URL has been clicked. The initial value will be 0, which will increment with each redirect.

      Save and close the file.

      To execute the schema.sql file to create the urls table, open a file named init_db.py inside your flask_shortener directory:

      Then add the following code:

      flask_shortener/init_db.py

      import sqlite3
      
      connection = sqlite3.connect('database.db')
      
      with open('schema.sql') as f:
          connection.executescript(f.read())
      
      connection.commit()
      connection.close()
      

      Here you connect to a file called database.db that your program will create once you execute this program. This file is the database that will hold all of your application’s data. You then open the schema.sql file and run it using the executescript() method that executes multiple SQL statements at once. This will create the urls table. Finally, you commit the changes and close the connection.

      Save and close the file.

      Run the program:

      After execution, a new file called database.db will appear in your flask_shortener directory.

      With this, you’ve installed Flask and the Hashids library, created the database schema, and created the SQLite database with a table called urls to store the URL shortener’s original URLs. Next, you’ll use Flask to create the index page where your users can enter a URL to generate a short URL.

      Step 2 — Creating the Index Page for Shortening URLs

      In this step, you will create a Flask route for the index page, which will allow users to enter a URL that you then save into the database. Your route will use the ID of the URL to generate a short string hash with the Hashids library, construct the short URL, and then render it as a result.

      First, open a file named app.py inside your flask_shortener directory. This is the main Flask application file:

      Add the following code to the file:

      flask_shortener/app.py

      import sqlite3
      from hashids import Hashids
      from flask import Flask, render_template, request, flash, redirect, url_for
      
      
      def get_db_connection():
          conn = sqlite3.connect('database.db')
          conn.row_factory = sqlite3.Row
          return conn
      

      In this code, you first import the sqlite3 module, the Hashids class from the hashids library, and Flask helpers.

      The get_db_connection() function opens a connection to the database.db database file and then sets the row_factory attribute to sqlite3.Row. As a result, you can have name-based access to columns; the database connection will return rows that behave like regular Python dictionaries. Lastly, the function returns the conn connection object you’ll be using to access the database.

      Next, add the following:

      flask_shortener/app.py

      . . .
      app = Flask(__name__)
      app.config['SECRET_KEY'] = 'this should be a secret random string'
      
      hashids = Hashids(min_length=4, salt=app.config['SECRET_KEY'])
      
      

      You create the Flask application object and set a secret key to secure sessions. Since the secret key is a secret random string, you’ll also use it to specify a salt for the Hashids library; this will ensure the hashes are unpredictable since every time the salt changes, the hashes also change.

      Note: A salt is a random string that is provided to the hashing function (that is, hashids.encode()) so that the resulting hash is shuffled based on the salt. This process ensures the hash you get is specific to your salt so that the hash is unique and unpredictable, like a secret password that only you can use to encode and decode hashes. Remember to keep it secret for security purposes (which is why you use the application’s secret key).

      You create a hashids object specifying that a hash should be at least 4 characters long by passing a value to the min_length parameter. You use the application’s secret key as a salt.

      Next, add the following code to the end of your file:

      flask_shortener/app.py

      . . .
      @app.route('/', methods=('GET', 'POST'))
      def index():
          conn = get_db_connection()
      
          if request.method == 'POST':
              url = request.form['url']
      
              if not url:
                  flash('The URL is required!')
                  return redirect(url_for('index'))
      
              url_data = conn.execute('INSERT INTO urls (original_url) VALUES (?)',
                                      (url,))
              conn.commit()
              conn.close()
      
              url_id = url_data.lastrowid
              hashid = hashids.encode(url_id)
              short_url = request.host_url + hashid
      
              return render_template('index.html', short_url=short_url)
      
          return render_template('index.html')
      

      The index() functions is a Flask view function, which is a function decorated using the special @app.route decorator. Its return value gets converted into an HTTP response that an HTTP client, such as a web browser, displays.

      Inside the index() view function, you accept both GET and POST requests by passing methods=('GET', 'POST') to the app.route() decorator. You open a database connection.

      Then if the request is a GET request, it skips the if request.method == 'POST' condition until the last line. This is where you render a template called index.html, which will contain a form for users to enter a URL to shorten.

      If the request is a POST request, the if request.method == 'POST' condition is true, which means a user has submitted a URL. You store the URL in the url variable; if the user has submitted an empty form, you flash the message The URL is required! and redirect to the index page.

      If the user has submitted a URL, you use the INSERT INTO SQL statement to store the submitted URL in the urls table. You include the ? placeholder in the execute() method and pass a tuple containing the submitted URL to insert data safely into the database. Then you commit the transaction and close the connection.

      In a variable called url_id, you store the ID of the URL you inserted into the database. You can access the ID of the URL using the lastrowid attribute, which provides the row ID of the last inserted row.

      You construct a hash using the hashids.encode() method, passing it the URL ID; you save the result in a variable called hashid. As an example, the call hashids.encode(1) might result in a unique hash like KJ34 depending on the salt you use.

      You then construct the short URL using request.host_url, which is an attribute that Flask’s request object provides to access the URL of the application’s host. This will be http://127.0.0.1:5000/ in a development environment and your_domain if you deploy your application. For example, the short_url variable will have a value like http://127.0.0.1:5000/KJ34, which is the short URL that will redirect your users to the original URL stored in the database with the ID that matches the hash KJ34.

      Lastly, you render the index.html template passing the short_url variable to it.

      After all the additions, the file will be as follows:

      flask_shortener/app.py

      import sqlite3
      from hashids import Hashids
      from flask import Flask, render_template, request, flash, redirect, url_for
      
      
      def get_db_connection():
          conn = sqlite3.connect('database.db')
          conn.row_factory = sqlite3.Row
          return conn
      
      
      app = Flask(__name__)
      app.config['SECRET_KEY'] = 'this should be a secret random string'
      
      hashids = Hashids(min_length=4, salt=app.config['SECRET_KEY'])
      
      
      @app.route('/', methods=('GET', 'POST'))
      def index():
          conn = get_db_connection()
      
          if request.method == 'POST':
              url = request.form['url']
      
              if not url:
                  flash('The URL is required!')
                  return redirect(url_for('index'))
      
              url_data = conn.execute('INSERT INTO urls (original_url) VALUES (?)',
                                      (url,))
              conn.commit()
              conn.close()
      
              url_id = url_data.lastrowid
              hashid = hashids.encode(url_id)
              short_url = request.host_url + hashid
      
              return render_template('index.html', short_url=short_url)
      
          return render_template('index.html')
      

      Save and close the file.

      Next, you’ll create a base template and the index.html template file.

      In your flask_shortener directory, create a templates directory and open a file called base.html inside it:

      • mkdir templates
      • nano templates/base.html

      Add the following code inside base.html. Note that, for styling, you’re using Bootstrap here too. If you are not familiar with HTML templates in Flask, see Step 3 of How To Make a Web Application Using Flask in Python 3:

      flask_shortener/templates/base.html

      <!doctype html>
      <html lang="en">
        <head>
          <!-- Required meta tags -->
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      
          <!-- Bootstrap CSS -->
          <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
      
          <title>{% block title %} {% endblock %}</title>
        </head>
        <body>
          <nav class="navbar navbar-expand-md navbar-light bg-light">
              <a class="navbar-brand" href="https://www.digitalocean.com/community/tutorials/{{ url_for("index')}}">FlaskShortener</a>
              <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                  <span class="navbar-toggler-icon"></span>
              </button>
              <div class="collapse navbar-collapse" id="navbarNav">
                  <ul class="navbar-nav">
                  <li class="nav-item active">
                      <a class="nav-link" href="#">About</a>
                  </li>
                  </ul>
              </div>
          </nav>
          <div class="container">
              {% for message in get_flashed_messages() %}
                  <div class="alert alert-danger">{{ message }}</div>
              {% endfor %}
              {% block content %} {% endblock %}
          </div>
      
          <!-- Optional JavaScript -->
          <!-- jQuery first, then Popper.js, then Bootstrap JS -->
          <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
          <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
        </body>
      </html>
      

      Most of the code in the preceding block is standard HTML and code required for Bootstrap. The <meta> tags provide information for the web browser, the <link> tag links the Bootstrap CSS files, and the <script> tags are links to JavaScript code that allows some additional Bootstrap features. Check out the Bootstrap documentation for more information.

      The <title>{% block title %} {% endblock %}</title> tag allows the inheriting templates to define a custom title. You use the for message in get_flashed_messages() loop to display the flashed messages (warnings, alerts, and so on). The {% block content %} {% endblock %} placeholder is where inheriting templates place the content so that all templates have access to this base template, which avoids repetition.

      Save and close the file.

      Next, create the index.html file that will extend this base.html file:

      • nano templates/index.html

      Add the following code to it:

      flask_shortener/templates/index.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Welcome to FlaskShortener {% endblock %}</h1>
          <form method="post">
          <div class="form-group">
              <label for="url">URL</label>
              <input type="text" name="url"
                     placeholder="URL to shorten" class="form-control"
                     value="{{ request.form['url'] }}" autofocus></input>
          </div>
      
          <div class="form-group">
              <button type="submit" class="btn btn-primary">Submit</button>
          </div>
          </form>
      
          {% if short_url %}
          <hr>
          <span>{{ short_url }}</span>
          {% endif %}
      {% endblock %}
      

      Here you extend base.html, define a title, and create a form with an input named url. The url input will allow users to enter URLs to shorten. It has a value of request.form['url'], which stores data in cases of submission failure; that is if the user provides no URL. You also add a submit button.

      Then you check if the short_url variable has any value—this is true if the form submits and the short URL generates successfully. If the condition is true, you display the short URL under the form.

      Set the environment variables Flask needs and run the application using the following commands:

      • export FLASK_APP=app
      • export FLASK_ENV=development
      • flask run

      The FLASK_APP environment variable specifies the application you want to run (the app.py file). The FLASK_ENV environment variable specifies the mode. development means that the application will run in development mode with the debugger running. Remember to avoid using this mode in production. You run the application using the flask run command.

      Open a browser and type in the URL http://127.0.0.1:5000/. You will find a Welcome to FlaskShortener page.

      Flask Shortener Index page

      Submit a URL, and you will receive a short URL.

      Flask Shortened URL displayed beneath the URL input box

      You created a Flask application with a page that accepts URLs and generates shorter ones, but the URLs don’t do anything yet. In the next step, you’ll add a route that extracts the hash from the short URL, finds the original URL, and redirects users to it.

      Step 3 — Adding the Redirect Route

      In this step, you will add a new route that takes the short hash the application generates and decodes the hash into its integer value, which is the original URL’s ID. Your new route will also use the integer ID to fetch the original URL and increment the clicks value. Finally, you will redirect users to the original URL.

      First, open the app.py to add a new route:

      Add the following code to the end of the file:

      flask_shortener/app.py

      . . .
      
      @app.route('/<id>')
      def url_redirect(id):
          conn = get_db_connection()
      
          original_id = hashids.decode(id)
          if original_id:
              original_id = original_id[0]
              url_data = conn.execute('SELECT original_url, clicks FROM urls'
                                      ' WHERE id = (?)', (original_id,)
                                      ).fetchone()
              original_url = url_data['original_url']
              clicks = url_data['clicks']
      
              conn.execute('UPDATE urls SET clicks = ? WHERE id = ?',
                           (clicks+1, original_id))
      
              conn.commit()
              conn.close()
              return redirect(original_url)
          else:
              flash('Invalid URL')
              return redirect(url_for('index'))
      

      This new route accepts a value id through the URL and passes it to the url_redirect() view function. For example, visiting http://127.0.0.1:5000/KJ34 would pass the string 'KJ34' to the id parameter.

      Inside the view function, you first open a database connection. Then you use the decode() method of the hashids object to convert the hash to its original integer value and store it in the original_id variable. You check that the original_id has a value—meaning decoding the hash was successful. If it has a value, you extract the ID from it. As the decode() method returns a tuple, you fetch the first value in the tuple with original_id[0], which is the original ID.

      You then use the SELECT SQL statement to fetch the original URL and its number of clicks from the urls table, where the ID of the URL matches the original ID you extracted from the hash. You fetch the URL data with the fetchone() method. Next, you extract the data into the two original_url and clicks variables.

      You then increment the number of clicks of the URL with the UPDATE SQL statement.

      You commit the transaction and close the connection, and redirect to the original URL using the redirect() Flask helper function.

      If decoding the hash fails, you flash a message to inform the user that the URL is invalid, and redirect them to the index page.

      Save and close the file.

      Run your development server:

      Use your browser to go to http://127.0.0.1:5000/. Enter a URL and visit the resulting short URL; your application will redirect you to the original URL.

      You created a new route that redirects users from the short URL to the original URL. Next, you’ll add a page to show how many times each URL has been visited.

      Step 4 — Adding a Statistics Page

      In this step, you’ll add a new route for a statistics page that displays how many times each URL has been clicked. You’ll also add a button that links to the page on the navigation bar.

      Allowing users to see the number of visits each shortened link has received will provide visibility into each URL’s popularity, which is useful for projects, like marketing ad campaigns. You can also use this workflow as an example of adding a feature to an existing Flask application.

      Open app.py to add a new route for a statistics page:

      Add the following code to the end of the file:

      flask_shortener/app.py

      . . .
      
      @app.route('/stats')
      def stats():
          conn = get_db_connection()
          db_urls = conn.execute('SELECT id, created, original_url, clicks FROM urls'
                                 ).fetchall()
          conn.close()
      
          urls = []
          for url in db_urls:
              url = dict(url)
              url['short_url'] = request.host_url + hashids.encode(url['id'])
              urls.append(url)
      
          return render_template('stats.html', urls=urls)
      

      In this view function, you open a database connection. Then you fetch the ID, the creation date, the original URL, and the number of clicks for all of the entries in the urls table. You use the fetchall() method to get a list of all the rows. You then save this data in the db_urls variable and close the connection.

      To display the short URL for each entry, you will need to construct it and add it to each item in the list of the URLs you fetched from the database (db_urls). You create an empty list called urls and loop through the db_urls list with for url in db_urls.

      You use the dict() Python function to convert the sqlite3.Row object to a dictionary to allow assignment. You add a new key called short_url to the dictionary with the value request.host_url + hashids.encode(url['id']), which is what you used before to construct short URLs in the index view function. You append this dictionary to the urls list.

      Finally, you render a template file called stats.html, passing the urls list to it.

      Save and close the file.

      Next, create the new stats.html template file:

      • nano templates/stats.html

      Type the following code into it:

      flask_shortener/templates/stats.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} FlaskShortener Statistics {% endblock %}</h1>
          <table class="table">
              <thead>
                  <tr>
                  <th scope="col">#</th>
                  <th scope="col">Short</th>
                  <th scope="col">Original</th>
                  <th scope="col">Clicks</th>
                  <th scope="col">Creation Date</th>
                  </tr>
              </thead>
              <tbody>
                  {% for url in urls %}
                      <tr>
                          <th scope="row">{{ url['id'] }}</th>
                          <td>{{ url['short_url'] }}</td>
                          <td>{{ url['original_url'] }}</td>
                          <td>{{ url['clicks'] }}</td>
                          <td>{{ url['created'] }}</td>
                      </tr>
                  {% endfor %}
              </tbody>
          </table>
      
      {% endblock %}
      

      Here you extend the base.html base template by specifying a title and defining a table with the following columns:

      • #: The ID of the URL.
      • Short: The short URL.
      • Original: The original URL.
      • Clicks: The number of times a short URL has been visited.
      • Creation Date: The creation date of the short URL.

      Each row is filled using a for loop that goes through the urls list and displays the value of each column for each URL.

      Run the development server with the following:

      Use your browser to go to http://127.0.0.1:5000/stats. You will find all the URLs in a table.

      Statistics page with list of URLs and number of clicks

      Next, add a Stats button to the navigation bar. Open the base.html file:

      Edit the file as per the following highlighted lines:

      flask_shortener/templates/base.html

      <!doctype html>
      <html lang="en">
        <head>
          <!-- Required meta tags -->
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      
          <!-- Bootstrap CSS -->
          <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
      
          <title>{% block title %} {% endblock %}</title>
        </head>
        <body>
          <nav class="navbar navbar-expand-md navbar-light bg-light">
              <a class="navbar-brand" href="https://www.digitalocean.com/community/tutorials/{{ url_for("index')}}">FlaskTodo</a>
              <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                  <span class="navbar-toggler-icon"></span>
              </button>
              <div class="collapse navbar-collapse" id="navbarNav">
                  <ul class="navbar-nav">
                  <li class="nav-item active">
                      <a class="nav-link" href="#">About</a>
                  </li>
      
                  <li class="nav-item active">
                      <a class="nav-link" href="https://www.digitalocean.com/community/tutorials/{{ url_for("stats')}}">Stats</a>
                  </li>
                  </ul>
              </div>
          </nav>
          <div class="container">
              {% for message in get_flashed_messages() %}
                  <div class="alert alert-danger">{{ message }}</div>
              {% endfor %}
              {% block content %} {% endblock %}
          </div>
      
          <!-- Optional JavaScript -->
          <!-- jQuery first, then Popper.js, then Bootstrap JS -->
          <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
          <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
        </body>
      </html>
      

      Here you incorporate a new <li> item to the navigation bar. You use the url_for() function to link to the stats() view function. You can now access the statistics page from the navigation bar.

      Your statistics page shows information about each URL, including its shorter equivalent and how many times it has been visited.

      You can reuse this code for monitoring number of clicks in other contexts, such as keeping track of how many times a post has been liked or updated on a social media site or how many times a photo/video has been viewed.

      You can access the full code for the application from this repository.

      Conclusion

      You have created a Flask application that allows users to enter a long URL and generate a shorter version. You have transformed integers into short string hashes, redirected users from one link to another, and set up a page for statistics so you can monitor shortened URLs. For further projects and tutorials on working with Flask, check out the following tutorials:



      Source link

      How To Create a URL Shortener with Django and GraphQL


      The author selected Girls Who Code to receive a donation as part of the Write for DOnations program.

      Introduction

      GraphQL is an API standard created and open-sourced by Facebook as an alternative to REST APIs. As opposed to REST APIs, GraphQL uses a typed system to define its data structure, where all the information sent and received must be compliant to a pre-defined schema. It also exposes a single endpoint for all communication instead of multiple URLs for different resources and solves the overfetching issue by returning only the data asked for by the client, thereby generating smaller and more concise responses.

      In this tutorial you will create a backend for a URL shortener—a service that takes any URL and generates a shorter, more readable version—while diving into GraphQL concepts, like queries and mutations, and tools, like the GraphiQL interface. You may already have used such services before, like bit.ly.

      Since GraphQL is a language agnostic technology, it is implemented on top of various languages and frameworks. Here, you will use the general purpose Python programming language, the Django web framework, and the Graphene-Django library as the GraphQL Python implementation with specific integrations for Django.

      Prerequisites

      Step 1 — Setting Up the Django Project

      In this step, you will be installing all the necessary tools for the application and setting up your Django project.

      Once you have created your project directory and started your virtual environment, as covered in the prerequisites, install the necessary packages using pip, the Python package manager. This tutorial will install Django version 2.1.7 and Graphene-Django version 2.2.0 or higher:

      • pip install "django==2.1.7" "graphene-django>==2.2.0"

      You now have all the tools needed in your tool belt. Next, you will create a Django project using the django-admin command. A project is the default Django boilerplate—a set of folders and files with everything necessary to start the development of a web application. In this case, you will call your project shorty and create it inside your current folder by specifying the . at the end:

      • django-admin startproject shorty .

      After creating your project, you will run the Django migrations. These files contain Python code generated by Django and are responsible for changing the application’s structure according to the Django models. Changes might include the creation of a table, for example. By default, Django comes with its own set of migrations responsible for subsystems like Django Authentication, so it is necessary to execute them with the following command:

      This command uses the Python interpreter to invoke a Django script called manage.py, responsible for managing different aspects of your project, like creating apps or running migrations.

      This will give output similar to the following:

      Output

      Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying sessions.0001_initial... OK

      Once Django’s database is ready to go, start its local development server:

      • python manage.py runserver

      This will give:

      Output

      Performing system checks... System check identified no issues (0 silenced). March 18, 2020 - 15:46:15 Django version 2.1.7, using settings 'shorty.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.

      This command will take away the prompt in your terminal and start the server.

      Visit the http://127.0.0.1:8000 page in your local browser. You will see this page:

      Django local server front page

      To stop the server and return to your terminal, press CTRL+C. Whenever you need to access the browser, make sure the preceding command is running.

      Next, you will finish this step by enabling the Django-Graphene library in the project. Django has the concept of app, a web application with a specific responsibility. A project is composed of one or multiple apps. For now, open the shorty/settings.py file in your text editor of choice. This tutorial will be using vim:

      The settings.py file manages all the settings in your project. Inside it, search for the INSTALLED_APPS entry and add the 'graphene_django' line:

      shorty/shorty/settings.py

      ...
      INSTALLED_APPS = [
          'django.contrib.admin',
          'django.contrib.auth',
          'django.contrib.contenttypes',
          'django.contrib.sessions',
          'django.contrib.messages',
          'django.contrib.staticfiles',
          'graphene_django',
      ]
      ...
      

      This addition tells Django that you will be using an app called graphene_django, which you installed in Step 1.

      At the bottom of the file, add the following variable:

      shorty/shorty/settings.py

      ...
      GRAPHENE = {
          'SCHEMA': 'shorty.schema.schema',
      }
      

      This last variable points to your main Schema, which you will create later. In GraphQL, a Schema contains all the object types, such as Resources, Queries, and Mutations. Think of it as documentation representing all the data and functionality available in your system.

      After the modifications, save and close the file.

      Now you have configured the Django project. In the next step, you will create a Django app and its Models.

      Step 2 — Setting Up a Django App and Models

      A Django platform is usually composed of one project and many applications or apps. An app describes a set of features inside a project, and, if well-designed, can be reused across Django projects.

      In this step, you will create an app called shortener, responsible for the actual URL shortening feature. To create its basic skeleton, type the next command in your terminal:

      • python manage.py startapp shortener

      Here you used the parameters startapp app_name, instructing manage.py to create an app named shortener.

      To finish the app creation, open the shorty/settings.py file

      Add the app’s name to the same INSTALLED_APPS entry you modified before:

      shorty/shorty/settings.py

      ...
      INSTALLED_APPS = [
          'django.contrib.admin',
          'django.contrib.auth',
          'django.contrib.contenttypes',
          'django.contrib.sessions',
          'django.contrib.messages',
          'django.contrib.staticfiles',
          'graphene_django'
          'shortener',
      ]
      ...
      

      Save and close the file.

      With your shortener added to shorty/settings.py, you can move on to creating the models for your project. Models are one of the key features in Django. They are used to represent a database in a “Pythonic” way, allowing you to manage, query, and store data using Python code.

      Before opening the models.py file for changes, this tutorial will give an overview of the changes you will make.

      Your model file—shortener/models.py—will contain the following content once you have replaced the existing code:

      shorty/shortener/models.py

      from hashlib import md5
      
      from django.db import models
      

      Here you will import the required packages needed by your code. You will add the line from hashlib import md5 at the top to import the Python standard library that will be used to create a hash of the URL. The from django.db import models line is a Django helper for creating models.

      Warning: This tutorial refers to hash as the result of a function that takes an input and always returns the same output. This tutorial will be using the MD5 hash function for demonstration purposes.

      Note that MD5 has collision issues and should be avoided in production.

      Next, you will add a Model named URL with the following fields:

      • full_url: the URL to be shortened.
      • url_hash: a short hash representing the full URL.
      • clicks: how many times the short URL was accessed.
      • created_at: the date and time at which the URL was created.

      shorty/shortener/models.py

      ...
      
      class URL(models.Model):
          full_url = models.URLField(unique=True)
          url_hash = models.URLField(unique=True)
          clicks = models.IntegerField(default=0)
          created_at = models.DateTimeField(auto_now_add=True)
      

      You will generate the url_hash by applying the MD5 hash algorithm to the full_url field and using just the first 10 characters returned during the Model’s save() method, executed every time Django saves an entry to the database. Additionally, URL shorteners usually track how many times a link was clicked. You will achieve this by calling the method clicked() when the URL is visited by a user.

      The operations mentioned will be added inside your URL model with this code:

      shorty/shortener/models.py

      ...
      
          def clicked(self):
              self.clicks += 1
              self.save()
      
          def save(self, *args, **kwargs):
              if not self.id:
                  self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]
      
              return super().save(*args, **kwargs)
      

      Now that you’ve reviewed the code, open the shortener/models.py file:

      Replace the code with the following content:

      shorty/shortener/models.py

      from hashlib import md5
      
      from django.db import models
      
      
      class URL(models.Model):
          full_url = models.URLField(unique=True)
          url_hash = models.URLField(unique=True)
          clicks = models.IntegerField(default=0)
          created_at = models.DateTimeField(auto_now_add=True)
      
          def clicked(self):
              self.clicks += 1
              self.save()
      
          def save(self, *args, **kwargs):
              if not self.id:
                  self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]
      
              return super().save(*args, **kwargs)
      

      Be sure to save and close the file.

      To apply these changes in the database, you will need to create the migrations by running the following command:

      • python manage.py makemigrations

      This will give you the following output:

      Output

      Migrations for 'shortener': shortener/migrations/0001_initial.py - Create model URL

      Then execute the migrations:

      You will see the following output in your terminal:

      Output

      Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, shortener Running migrations: Applying shortener.0001_initial... OK

      Now that you’ve set up the models, in the next step you will create the GraphQL endpoint and a Query.

      Step 3 — Creating Queries

      The REST architecture exposes different resources in different endpoints, each one containing a well-defined data structure. For example, you may fetch a users’ list at /api/users, always expecting the same fields. GraphQL, on the other hand, has a single endpoint for all interactions, and uses Queries to access data. The main—and most valuable—difference is that you can use a Query to retrieve all your users within a single request.

      Start by creating a Query to fetch all URLs. You will need a couple of things:

      • A URL type, linked to your previously defined model.
      • A Query statement named urls.
      • A method to resolve your Query, meaning to fetch all URLs from the database and return them to the client.

      Create a new file called shortener/schema.py:

      Start by adding the Python import statements:

      shorty/shortener/schema.py

      import graphene
      from graphene_django import DjangoObjectType
      
      from .models import URL
      

      The first line imports the main graphene library, which contains the base GraphQL types, like List. The DjangoObjectType is a helper to create a Schema definition from any Django model, and the third line imports your previously create URL model.

      After that, create a new GraphQL type for the URL model by adding the following lines:

      shorty/shortener/schema.py

      ...
      class URLType(DjangoObjectType):
          class Meta:
              model = URL
      

      Finally, add these lines to create a Query type for the URL model:

      shorty/shortener/schema.py

      ...
      class Query(graphene.ObjectType):
          urls = graphene.List(URLType)
      
          def resolve_urls(self, info, **kwargs):
              return URL.objects.all()
      

      This code creates a Query class with one field named urls, which is a list of the previously defined URLType. When resolving the Query through the resolve_urls method, you return all the URLs stored in the database.

      The full shortener/schema.py file is shown here:

      shorty/shortener/schema.py

      import graphene
      from graphene_django import DjangoObjectType
      
      from .models import URL
      
      
      class URLType(DjangoObjectType):
          class Meta:
              model = URL
      
      
      class Query(graphene.ObjectType):
          urls = graphene.List(URLType)
      
          def resolve_urls(self, info, **kwargs):
              return URL.objects.all()
      

      Save and close the file.

      All the Queries must now be added to the main Schema. Think of it as a holder for all your resources.

      Create a new file in the shorty/schema.py path and open it with your editor:

      Import the following Python packages by adding the following lines. The first one, as already mentioned, contains the base GraphQL types. The second line imports the previously created Schema file.

      shorty/shorty/schema.py

      import graphene
      
      import shortener.schema
      

      Next, add the main Query class. It will hold, via inheritance, all the Queries and future operations created:

      shorty/shorty/schema.py

      ...
      class Query(shortener.schema.Query, graphene.ObjectType):
          pass
      

      Lastly, create the schema variable:

      shorty/shorty/schema.py

      ...
      schema = graphene.Schema(query=Query)
      

      The SCHEMA setting you defined in Step 2 points to the schema variable you’ve just created.

      The full shorty/schema.py file is shown here:

      shorty/shorty/schema.py

      import graphene
      
      import shortener.schema
      
      
      class Query(shortener.schema.Query, graphene.ObjectType):
          pass
      
      schema = graphene.Schema(query=Query)
      

      Save and close the file.

      Next, enable the GraphQL endpoint and the GraphiQL interface, which is a graphical web interface used to interact with the GraphQL system.

      Open the shorty/urls.py file:

      For learning purposes, delete the file contents and save it, so that you can start from scratch.

      The first lines you will add are Python import statements:

      shorty/shorty/urls.py

      from django.urls import path
      from django.views.decorators.csrf import csrf_exempt
      
      from graphene_django.views import GraphQLView
      

      The path function is used by Django to create an accessible URL for the GraphiQL interface. Following it, you import the csrf_exempt, which allows clients to send data to the server. A complete explanation can be found in the Graphene Documentation. In the last line, you imported the actual code responsible for the interface via GraphQLView.

      Next, create a variable named urlpatterns.

      shorty/shorty/urls.py

      ...
      urlpatterns = [
          path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
      ]
      

      This will stitch together all the code necessary to make the GraphiQL interface available in the graphql/ path:

      The full shortener/urls.py file is shown here:

      shorty/shorty/urls.py

      from django.urls import path
      from django.views.decorators.csrf import csrf_exempt
      
      from graphene_django.views import GraphQLView
      
      urlpatterns = [
          path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
      ]
      

      Save the file and close it.

      Back in the terminal, run the python manage.py runserver command (if not running already):

      • python manage.py runserver

      Open your web browser at the http://localhost:8000/graphql address. You will be presented with this screen:

      GraphiQL interface

      The GraphiQL is an interface where you can run GraphQL statements and see the results. One feature is the Docs section on the top right. Since everything in GraphQL is typed, you get free documentation about all your Types, Queries, Mutations, etc.

      After exploring the page, insert your first Query on the main text area:

      query {
        urls {
          id
          fullUrl
          urlHash
          clicks
          createdAt
        }
      }
      

      This content shows how a GraphQL Query is structured: First, you use the keyword query to tell the server that you only want some data back. Next, you use the urls field defined in the shortener/schema.py file inside the Query class. From that, you explicitly request all the fields defined in the URL model using camel case-style, which is the default for GraphQL.

      Now, click on the play arrow button in the top left.

      You will receive the following response, stating that you still have no URLs:

      Output

      { "data": { "urls": [] } }

      This shows that GraphQL is working. In your terminal, press CTRL+C to stop your server.

      You have accomplished a lot in this step, creating the GraphQL endpoint, making a Query to fetch all URLs, and enabling the GraphiQL interface. Now, you will create Mutations to change the database.

      Step 4 — Creating Mutations

      The majority of applications have a way to change the database state by adding, updating, or deleting data. In GraphQL, these operations are called Mutations. They look like Queries but use arguments to send data to the server.

      To create your first Mutation, open shortener/schema.py:

      At the end of the file, start by adding a new class named CreateURL:

      shorty/shortener/schema.py

      ...
      class CreateURL(graphene.Mutation):
          url = graphene.Field(URLType)
      

      This class inherits the graphene.Mutation helper to have the capabilities of a GraphQL Mutation. It also has a property name url, defining the content returned by the server after the Mutation is completed. In this case, it will be the URLType data structure.

      Next, add a subclass named Arguments to the already defined class:

      shorty/shortener/schema.py

      ...
          class Arguments:
              full_url = graphene.String()
      

      This defines what data will be accepted by the server. Here, you are expecting a parameter named full_url with a String content:

      Now add the following lines to create the mutate method:

      shorty/shortener/schema.py

      ...
      
          def mutate(self, info, full_url):
              url = URL(full_url=full_url)
              url.save()
      

      This mutate method does a lot of the work by receiving the data from the client and saving it to the database. In the end, it returns the class itself containing the newly created item.

      Lastly, create a Mutation class to hold all the Mutations for your app by adding these lines:

      shorty/shortener/schema.py

      ...
      
      class Mutation(graphene.ObjectType):
          create_url = CreateURL.Field()
      

      So far, you will only have one mutation named create_url.

      The full shortener/schema.py file is shown here:

      shorty/shortener/schema.py

      import graphene
      from graphene_django import DjangoObjectType
      
      from .models import URL
      
      
      class URLType(DjangoObjectType):
          class Meta:
              model = URL
      
      
      class Query(graphene.ObjectType):
          urls = graphene.List(URLType)
      
          def resolve_urls(self, info, **kwargs):
              return URL.objects.all() 
      
      
      class CreateURL(graphene.Mutation):
          url = graphene.Field(URLType)
      
          class Arguments:
              full_url = graphene.String()
      
          def mutate(self, info, full_url):
              url = URL(full_url=full_url)
              url.save()
      
              return CreateURL(url=url)
      
      
      class Mutation(graphene.ObjectType):
          create_url = CreateURL.Field()
      

      Close and save the file.

      To finish adding the Mutation, change the shorty/schema.py file:

      Alter the file to include the following highlighted code:

      shorty/shorty/schema.py

      
      import graphene
      
      import shortener.schema
      
      
      class Query(shortener.schema.Query, graphene.ObjectType):
          pass
      
      
      class Mutation(shortener.schema.Mutation, graphene.ObjectType):
          pass
      
      
      schema = graphene.Schema(query=Query, mutation=Mutation)
      

      Save and close the file. If you are not running the local server, start it:

      • python manage.py runserver

      Navigate to http://localhost:8000/graphql in your web browser. Execute your first Mutation in the GraphiQL web interface by running the following statement:

      mutation {
        createUrl(fullUrl:"https://www.digitalocean.com/community") {
          url {
            id
            fullUrl
            urlHash
            clicks
            createdAt
          }
        }
      }
      

      You composed the Mutation with the createURL name, the fullUrl argument, and the data you want in the response defined inside the url field.

      The output will contain the URL information you just created inside the GraphQL data field, as shown here:

      Output

      { "data": { "createUrl": { "url": { "id": "1", "fullUrl": "https://www.digitalocean.com/community", "urlHash": "077880af78", "clicks": 0, "createdAt": "2020-01-30T19:15:10.820062+00:00" } } } }

      With that, a URL was added to the database with its hashed version, as you can see in the urlHash field. Try running the Query you created in the last Step to see its result:

      query {
        urls {
          id
          fullUrl
          urlHash
          clicks
          createdAt
        }
      }
      

      The output will show the stored URL:

      Output

      { "data": { "urls": [ { "id": "1", "fullUrl": "https://www.digitalocean.com/community", "urlHash": "077880af78", "clicks": 0, "createdAt": "2020-03-18T21:03:24.664934+00:00" } ] } }

      You can also try executing the same Query, but only asking for the fields you want.

      Next, try it one more time with a different URL:

      mutation {
        createUrl(fullUrl:"https://www.digitalocean.com/write-for-donations/") {
          url {
            id
            fullUrl
            urlHash
            clicks
            createdAt
          }
        }
      }
      

      The output will be:

      Output

      { "data": { "createUrl": { "url": { "id": "2", "fullUrl": "https://www.digitalocean.com/write-for-donations/", "urlHash": "703562669b", "clicks": 0, "createdAt": "2020-01-30T19:31:10.820062+00:00" } } } }

      The system is now able to create short URLs and list them. In the next step, you will enable users to access a URL by its short version, redirecting them to the correct page.

      Step 5 — Creating the Access Endpoint

      In this step, you will use Django Views—a method that takes a request and returns a response—to redirect anyone accessing the http://localhost:8000/url_hash endpoint to its full URL.

      Open the shortener/views.py file with your editor:

      To start, import two packages by replacing the contents with the following lines:

      shorty/shortener/views.py

      from django.shortcuts import get_object_or_404, redirect
      
      from .models import URL
      

      These will be explained more thoroughly later on.

      Next, you will create a Django View named root. Add this code snippet responsible for the View at the end of your file:

      shorty/shortener/views.py

      ...
      
      def root(request, url_hash):
          url = get_object_or_404(URL, url_hash=url_hash)
          url.clicked()
      
          return redirect(url.full_url)
      

      This receives an argument called url_hash from the URL requested by a user. Inside the function, the first line tries to get the URL from the database using the url_hash argument. If not found, it returns the HTTP 404 error to the client, which means that the resource is missing. Afterwards, it increments the clicked property of the URL entry, making sure to track how many times the URL is accessed. At the end, it redirects the client to the requested URL.

      The full shortener/views.py file is shown here:

      shorty/shortener/views.py

      from django.shortcuts import get_object_or_404, redirect
      
      from .models import URL
      
      
      def root(request, url_hash):
          url = get_object_or_404(URL, url_hash=url_hash)
          url.clicked()
      
          return redirect(url.full_url)
      

      Save and close the file.

      Next, open shorty/urls.py:

      Add the following highlighted code to enable the root View.

      shorty/shorty/urls.py

      
      from django.urls import path
      from django.views.decorators.csrf import csrf_exempt
      
      from graphene_django.views import GraphQLView
      
      from shortener.views import root
      
      
      urlpatterns = [
          path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
          path('<str:url_hash>/', root, name='root'),
      ]
      

      The root View will be accessible in the / path of your server, accepting a url_hash as a string parameter.

      Save and close the file. If you are not running the local server, start it by executing the python manage.py runserver command.

      To test your new addition, open your web browser and access the http://localhost:8000/077880af78 URL. Note that the last part of the URL is the hash created by the Mutation from Step 5. You will be redirected to the hash’s URL page, in this case, the DigitalOcean Community website.

      Now that you have the URL redirection working, you will make the application safer by implementing error handling when the Mutation is executed.

      Step 6 — Implementing Error Handling

      Handling errors is a best practice in all applications, since developers don’t usually control what will be sent to the server. In this case, you can try to foresee failures and minimize their impacts. In a complex system such as GraphQL, a lot of things might go wrong, from the client asking for the wrong data to the server losing access to the database.

      As a typed system, GraphQL can verify everything the client asks for and receives in an operation called Schema Validation. You can see this in action by making a Query with a non-existing field.

      Navigate to http://localhost:8000/graphql in your browser once more, and execute the next Query within the GraphiQL interface, with the iDontExist field:

      query {
        urls {
          id
          fullUrl
          urlHash
          clicks
          createdAt
          iDontExist
        }
      }
      

      Since there is no iDontExist field defined in your Query, GraphQL returns an error message:

      Output

      { "errors": [ { "message": "Cannot query field "iDontExist" on type "URLType".", "locations": [ { "line": 8, "column": 5 } ] } ] }

      This is important because, in the GraphQL typed system, the aim is to send and receive just the information already defined in the schema.

      The current application accepts any arbitrary string in the full_url field. The problem is that if someone sends a poorly constructed URL, you would be redirecting the user to nowhere when trying the stored information. In this case, you need to verify if the full_url is well formatted before saving it to the database, and, if there’s any error, raise the GraphQLError exception with a custom message.

      Let’s implement this functionality in two steps. First, open the shortener/models.py file:

      Add the highlighted lines in the import section:

      shorty/shortener/models.py

      from hashlib import md5
      
      from django.db import models
      from django.core.validators import URLValidator
      from django.core.exceptions import ValidationError
      
      from graphql import GraphQLError
      ...
      

      The URLValidator is a Django helper to validate a URL String and the GraphQLError is used by Graphene to raise exceptions with a custom message.

      Next, make sure to validate the URL received by the user before saving it to the database. Enable this operation by adding the highlighted code in the shortener/models.py file:

      shorty/shortener/models.py

      class URL(models.Model):
          full_url = models.URLField(unique=True)
          url_hash = models.URLField(unique=True)
          clicks = models.IntegerField(default=0)
          created_at = models.DateTimeField(auto_now_add=True)
      
          def clicked(self):
              self.clicks += 1
              self.save()
      
          def save(self, *args, **kwargs):
              if not self.id:
                  self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]
      
              validate = URLValidator()
              try:
                  validate(self.full_url)
              except ValidationError as e:
                  raise GraphQLError('invalid url')
      
              return super().save(*args, **kwargs)
      

      First, this code instantiates the URLValidator in the validate variable. Inside the try/except block, you validate() the URL received and raise a GraphQLError with the invalid url custom message if something went wrong.

      The full shortener/models.py file is shown here:

      shorty/shortener/models.py

      from hashlib import md5
      
      from django.db import models
      from django.core.validators import URLValidator
      from django.core.exceptions import ValidationError
      
      from graphql import GraphQLError
      
      
      class URL(models.Model):
          full_url = models.URLField(unique=True)
          url_hash = models.URLField(unique=True)
          clicks = models.IntegerField(default=0)
          created_at = models.DateTimeField(auto_now_add=True)
      
          def clicked(self):
              self.clicks += 1
              self.save()
      
          def save(self, *args, **kwargs):
              if not self.id:
                  self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]
      
              validate = URLValidator()
              try:
                  validate(self.full_url)
              except ValidationError as e:
                  raise GraphQLError('invalid url')
      
              return super().save(*args, **kwargs)
      

      Save and close the file. If you are not running the local server, start it with the python manage.py runserver command.

      Next, test your new error handling at http://localhost:8000/graphql. Try to create a new URL with an invalid full_url in the GraphiQL interface:

      mutation {
        createUrl(fullUrl:"not_valid_url"){
          url {
            id
            fullUrl
            urlHash
            clicks
            createdAt
          }
        }
      }
      

      When sending an invalid URL, your exception will be raised with the custom message:

      Output

      { "errors": [ { "message": "invalid url", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createUrl" ] } ], "data": { "createUrl": null } }

      If you look in your terminal where the python manage.py runserver command is running, an error will appear:

      Output

      ... graphql.error.located_error.GraphQLLocatedError: invalid url [30/Jan/2020 19:46:32] "POST /graphql/ HTTP/1.1" 200 121

      A GraphQL endpoint will always fail with a HTTP 200 status code, which usually signifies success. Remember that, even though GraphQL is built on top of HTTP, it doesn’t use the concepts of HTTP status codes or HTTP methods as REST does.

      With the error handling implemented, you can now put in place a mechanism to filter your Queries, minimizing the information returned by the server.

      Step 7 — Implementing Filters

      Imagine you’ve started using the URL shortener to add your own links. After a while, there will be so many entries that finding the right one will become difficult. You can solve this issue using filters.

      Filtering is a common concept in REST APIs, where usually a Query Parameter with a field and value is appended to the URL. As an example, to filter all the Users named jojo, you could use GET /api/users?name=jojo.

      In GraphQL you will use Query Arguments as filters. They create a nice and clean interface.

      You can solve the “hard to find a URL” issue by allowing the client to filter URLs by name using the full_url field. To implement that, open the shortener/schema.py file in your favorite editor.

      First, import the Q method in the highlighted line:

      shorty/shortener/schema.py

      import graphene
      from graphene_django import DjangoObjectType
      from django.db.models import Q
      
      from .models import URL
      ...
      

      This will be used to filter your database query.

      Next, rewrite the whole Query class with the following content:

      shorty/shortener/schema.py

      ...
      class Query(graphene.ObjectType):
          urls = graphene.List(URLType, url=graphene.String())
      
          def resolve_urls(self, info, url=None, **kwargs):
              queryset = URL.objects.all()
      
              if url:
                  _filter = Q(full_url__icontains=url)
                  queryset = queryset.filter(_filter)
      
              return queryset
      ...
      

      The modifications you are making are:

      • Adding the url filter parameter inside the urls variable and resolve_url method.
      • Inside the resolve_urls, if a parameter named url is given, filtering the database results to return only URLs that contain the value given, using the Q(full_url__icontains=url) method.

      The full shortener/schema.py file is shown here:

      shorty/shortener/schema.py

      import graphene
      from graphene_django import DjangoObjectType
      from django.db.models import Q
      
      from .models import URL
      
      
      class URLType(DjangoObjectType):
          class Meta:
              model = URL
      
      
      class Query(graphene.ObjectType):
          urls = graphene.List(URLType, url=graphene.String())
      
          def resolve_urls(self, info, url=None, **kwargs):
              queryset = URL.objects.all()
      
              if url:
                  _filter = Q(full_url__icontains=url)
                  queryset = queryset.filter(_filter)
      
              return queryset
      
      
      class CreateURL(graphene.Mutation):
          url = graphene.Field(URLType)
      
          class Arguments:
              full_url = graphene.String()
      
          def mutate(self, info, full_url)
              url = URL(full_url=full_url)
              url.save()
      
              return CreateURL(url=url)
      
      
      class Mutation(graphene.ObjectType):
          create_url = CreateURL.Field() 
      

      Save and close the file. If you are not running the local server, start it with python manage.py runserver.

      Test your latest changes at http://localhost:8000/graphql. In the GraphiQL interface, write the following statement. It will filter all the URLs with the word community:

      query {
        urls(url:"community") {
          id
          fullUrl
          urlHash
          clicks
          createdAt
        }
      }
      

      The output is only one entry since you just added one URL with the community string in it. If you added more URLs before, your output may vary.

      Output

      { "data": { "urls": [ { "id": "1", "fullUrl": "https://www.digitalocean.com/community", "urlHash": "077880af78", "clicks": 1, "createdAt": "2020-01-30T19:27:36.243900+00:00" } ] } }

      Now you have the ability to search through your URLs. However, with too many links, your clients might complain the URL list is returning more data than their apps can handle. To solve this, you will implement pagination.

      Clients using your backend might complain that the response time is taking too long or that its size is too big if there are too many URL entries. Even your database may struggle to put together a huge set of information. To solve this issue, you can allow the client to specify how many items it wants within each request using a technique called pagination.

      There’s no default way to implement this feature. Even in REST APIs, you might see it in HTTP headers or query parameters, with different names and behaviors.

      In this application, you will implement pagination by enabling two more arguments to the URLs Query: first and skip. first will select the first variable number of elements and skip will specify how many elements should be skipped from the beginning. For example, using first == 10 and skip == 5 gets the first 10 URLs, but skips 5 of them, returning just the remaining 5.

      Implementing this solution is similar to adding a filter.

      Open the shortener/schema.py file:

      In the file, change the Query class by adding the two new parameters into the urls variable and resolve_urls method, highlighted in the following code:

      shorty/shortener/schema.py

      import graphene
      from graphene_django import DjangoObjectType
      from django.db.models import Q
      
      from .models import URL
      
      
      class Query(graphene.ObjectType):
          urls = graphene.List(URLType, url=graphene.String(), first=graphene.Int(), skip=graphene.Int())
      
          def resolve_urls(self, info, url=None, first=None, skip=None, **kwargs):
              queryset = URL.objects.all()
      
              if url:
                  _filter = Q(full_url__icontains=url)
                  queryset = queryset.filter(_filter)
      
              if first:
                  queryset = queryset[:first]
      
              if skip:
                  queryset = queryset[skip:]
      
              return queryset
      ...
      

      This code uses the newly created first and skip parameters inside the resolve_urls method to filter the database query.

      Save and close the file. If you are not running the local server, start it with python manage.py runserver.

      To test the pagination, issue the following Query in the GraphiQL interface at http://localhost:8000/graphql:

      query {
        urls(first: 2, skip: 1) {
          id
          fullUrl
          urlHash
          clicks
          createdAt
        }
      }
      

      Your URL shortener will return the second URL created in your database:

      Output

      { "data": { "urls": [ { "id": "2", "fullUrl": "https://www.digitalocean.com/write-for-donations/", "urlHash": "703562669b", "clicks": 0, "createdAt": "2020-01-30T19:31:10.820062+00:00" } ] } }

      This shows that the pagination feature works. Feel free to play around by adding more URLs and testing different sets of first and skip.

      Conclusion

      The whole GraphQL ecosystem is growing every day, with an active community behind it. It has been proven production-ready by companies like GitHub and Facebook, and now you can apply this technology to your own projects.

      In this tutorial you created a URL shortener service using GraphQL, Python, and Django, using concepts like Queries and Mutations. But more than that, you now understand how to rely on these technologies to build web applications using the Django web framework.

      You can explore more about GraphQL and the tools used here in the GraphQL website and the Graphene documentation websites. Also, DigitalOcean has additional tutorials for Python and Django that you can use if you’d like to learn more about either.



      Source link