URL shortener
In this tutorial, you will create a URL shortener using Yandex Cloud serverless technology.
This tool accepts user requests via a public API gateway. The user receives an HTML page with a URL input field from hosting. The function sends the entered URL for storage in a serverless database, shortens it, and returns it to the user. When the user follows the shortened URL, the function retrieves the full URL from the database and and performs a redirect.
To configure and test the tool:
- Get your cloud ready.
- Set up hosting for the URL shortener page.
- Create a service account.
- Create a database in Yandex Managed Service for YDB.
- Set up a function in Yandex Cloud Functions.
- Publish your URL shortener via Yandex API Gateway.
- Test the URL shortener.
If you no longer need the resources you created, delete them.
Get your cloud ready
Sign up for Yandex Cloud and create a billing account:
- Navigate to the management console
and log in to Yandex Cloud or create a new account. - On the Yandex Cloud Billing
page, make sure you have a billing account linked and it has theACTIVE
orTRIAL_ACTIVE
status. If you do not have a billing account, create one and link a cloud to it.
If you have an active billing account, you can navigate to the cloud page
Learn more about clouds and folders.
Required paid resources
The cost of resources for our scenario includes:
- Fee for using the storage (see Yandex Object Storage pricing).
- Fee for database queries (see Managed Service for YDB pricing).
- Fee for function invocations (see Cloud Functions pricing).
- Fee for API gateway requests (see API Gateway pricing).
Set up hosting for the URL shortener page
To create a bucket, upload your URL shortener's HTML page to it, and configure it for static website hosting:
-
In the management console
, select your working folder. -
Select Object Storage.
-
Click Create bucket.
-
On the bucket creation page:
-
Enter a name for the bucket.
Warning
Bucket names are unique throughout Object Storage, which means you cannot create two buckets with the same name, even in different folders belonging to different clouds.
-
Set the maximum size to
1 GB
. -
Set object read access to
Public
. -
Click Create bucket to complete the operation.
-
-
Copy the HTML code and paste it into
index.html
:HTML code
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>URL shortener</title> <!-- warns against sending unnecessary GET requests to /favicon.ico --> <link rel="icon" href="data:;base64,iVBORw0KGgo="> </head> <body> <h1>Welcome</h1> <form action="javascript:shorten()"> <label for="url">Enter a link:</label><br> <input id="url" name="url" type="text"><br> <input type="submit" value="Shorten"> </form> <p id="shortened"></p> </body> <script> function shorten() { const link = document.getElementById("url").value fetch("/shorten", { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: link }) .then(response => response.json()) .then(data => { const url = data.url document.getElementById("shortened").innerHTML = `<a href=${url}>${url}</a>` }) .catch(error => { document.getElementById("shortened").innerHTML = `<p>An error (${error}) occurred. Try again.</p>` }) } </script> </html>
-
Click the name of the bucket you created.
-
Click Upload objects.
-
Specify the
index.html
file you set up earlier. -
Click Upload.
-
In the left-hand panel, select Settings.
-
On the Website tab:
- Select
Hosting
. - Specify the website's home page:
index.html
.
- Select
-
Click Save.
Create a service account
To create a service account for communication between the shortener components:
-
Navigate to your working folder.
-
In the list of services, select Identity and Access Management.
-
Click Create service account.
-
Specify the service account name:
serverless-shortener
. -
Click Add role and select
editor
. -
Click Create.
-
Click the name of the service account you created.
Save the service account ID, as you will need it later.
Create a database in Managed Service for YDB
To create a database in Managed Service for YDB and configure it to store URLs:
-
Navigate to your working folder.
-
In the list of services, select Managed Service for YDB.
-
Click Create a database.
-
Enter the database name:
for-serverless-shortener
. -
Select Serverless as the database type.
-
Click Create a database.
-
Wait until the database is up and running.
While being created, your database will have the
Provisioning
status. Once it is ready for use, its status will change toRunning
. -
Click the database name.
Save the Endpoint field value, as you will need it later.
-
In the left-hand panel, select the Navigation tab.
-
Click New SQL query.
-
Copy the following SQL query and paste it into the Query field:
CREATE TABLE links ( id Utf8, link Utf8, PRIMARY KEY (id) );
-
Click Run.
Set up a function in Cloud Functions
To create and set up a URL shortening function:
-
Navigate to your working folder.
-
In the list of services, select Cloud Functions.
-
Click Create function.
-
Enter the name:
for-serverless-shortener
. -
Click Create.
-
From the Python drop-down list, select the
python312
runtime environment. -
Click Continue.
-
Copy the function code and paste it into
index.py
under Function code.Function code
import ydb import urllib.parse import hashlib import base64 import json import os def decode(event, body): # The request body can be encoded. is_base64_encoded = event.get('isBase64Encoded') if is_base64_encoded: body = str(base64.b64decode(body), 'utf-8') return body def response(statusCode, headers, isBase64Encoded, body): return { 'statusCode': statusCode, 'headers': headers, 'isBase64Encoded': isBase64Encoded, 'body': body, } def get_config(): endpoint = os.getenv("endpoint") database = os.getenv("database") if endpoint is None or database is None: raise AssertionError("You need to specify both environment variables") credentials = ydb.iam.MetadataUrlCredentials() return ydb.DriverConfig(endpoint, database, credentials=credentials) def execute(config, query, params): with ydb.Driver(config) as driver: try: driver.wait(timeout=5, fail_fast=True) except TimeoutError: print("Connect failed to YDB") print("Last reported errors by discovery:") print(driver.discovery_debug_details()) return None session = driver.table_client.session().create() prepared_query = session.prepare(query) return session.transaction(ydb.SerializableReadWrite()).execute( prepared_query, params, commit_tx=True ) def insert_link(id, link): config = get_config() query = """ DECLARE $id AS Utf8; DECLARE $link AS Utf8; UPSERT INTO links (id, link) VALUES ($id, $link); """ params = {'$id': id, '$link': link} execute(config, query, params) def find_link(id): print(id) config = get_config() query = """ DECLARE $id AS Utf8; SELECT link FROM links where id=$id; """ params = {'$id': id} result_set = execute(config, query, params) if not result_set or not result_set[0].rows: return None return result_set[0].rows[0].link def shorten(event): body = event.get('body') if body: body = decode(event, body) original_host = event.get('headers').get('Origin') link_id = hashlib.sha256(body.encode('utf8')).hexdigest()[:6] # The URL may contain encoded characters, e.g., %. This can interfere with API Gateway during redirection. # To fix this, decode the URL by invoking `urllib.parse.unquote`. insert_link(link_id, urllib.parse.unquote(body)) return response(200, {'Content-Type': 'application/json'}, False, json.dumps({'url': f'{original_host}/r/{link_id}'})) return response(400, {}, False, 'The url parameter is missing in the request body') def redirect(event): link_id = event.get('pathParams').get('id') redirect_to = find_link(link_id) if redirect_to: return response(302, {'Location': redirect_to}, False, '') return response(404, {}, False, 'This link does not exist') # These checks are required because we have only one function. # Ideally, each path in API Gateway should have a function of its own. def get_result(url, event): if url == "/shorten": return shorten(event) if url.startswith("/r/"): return redirect(event) return response(404, {}, False, 'This path does not exist') def handler(event, context): url = event.get('url') if url: # The URL may come from API Gateway with a question mark at the end. if url[-1] == '?': url = url[:-1] return get_result(url, event) return response(404, {}, False, 'This function should be invoked using API Gateway.')
-
Under Function code, create a file named
requirements.txt
and paste the following text into it:ydb
-
Specify the entry point:
index.handler
. -
Set the timeout to
5
. -
Select the
serverless-shortener
service account. -
Add these environment variables:
endpoint
: Enter the first part of the previously saved Endpoint field value (preceding/?database=
), e.g.,grpcs://ydb.serverless.yandexcloud.net:2135
.database
: Enter the second part of the previously saved Endpoint field value (following/?database=
), e.g.,/ru-central1/r1gra875baom********/g5n22e7ejfr1********
.
-
Click Save changes.
-
Under Overview, enable Public function.
Save the function ID, as you will need it later.
Publish your URL shortener via API Gateway
To publish your URL shortener via API Gateway:
-
Navigate to your working folder.
-
In the list of services, select API Gateway.
-
Click Create API gateway.
-
In the Name field, enter
for-serverless-shortener
. -
Copy and paste the following code under Specification:
Specification
openapi: 3.0.0 info: title: for-serverless-shortener version: 1.0.0 paths: /: get: x-yc-apigateway-integration: type: object_storage bucket: <bucket_name> # <-- bucket name object: index.html # <-- HTML file name presigned_redirect: false service_account: <service_account_id> # <-- service account ID operationId: static /shorten: post: x-yc-apigateway-integration: type: cloud_functions function_id: <function_id> # <-- function ID operationId: shorten /r/{id}: get: x-yc-apigateway-integration: type: cloud_functions function_id: <function_id> # <-- function ID operationId: redirect parameters: - description: id of the url explode: false in: path name: id required: true schema: type: string style: simple
Edit the specification code as follows:
- Replace
<service_account_id>
with the ID of the service account you created earlier. - Replace
<function_id>
with the ID of the function you created earlier. - Replace
<bucket_name>
with the name of the bucket you created earlier.
- Replace
-
Click Create.
-
Click the name of the API gateway you created.
-
Copy the
url
value from the specification.Use this URL to access your URL shortener.
Test the URL shortener
To make sure your shortener components communicate properly:
-
In your browser, open the previously copied URL.
-
In the input field, enter the URL you want to shorten.
-
Click Shorten.
You will see the shortened URL below.
-
Open the shortened URL. It should lead to the same page as the original link.
How to delete the resources you created
To stop paying for the resources you created: