In this article, we’ll create a URL Shortener using FastAPI for the backend and ReactJs and TailwindCSS for the frontend design.
This code is a simple implementation of a URL shortening service using the FastAPI framework in Python. Let’s break down the code and understand its functionality:
Backend with FastAPI
First create a virtual environment and install the dependencies
python <span>-m</span> venv <span>env source env</span>/bin/activatepython <span>-m</span> pip <span>install </span>fastapi uvicornpython <span>-m</span> venv <span>env source env</span>/bin/activate python <span>-m</span> pip <span>install </span>fastapi uvicornpython -m venv env source env/bin/activate python -m pip install fastapi uvicorn
Enter fullscreen mode Exit fullscreen mode
Then, let’s import the necessary imports needed
<span>from</span> <span>fastapi</span> <span>import</span> <span>FastAPI</span><span>,</span> <span>HTTPException</span><span>from</span> <span>fastapi.middleware.cors</span> <span>import</span> <span>CORSMiddleware</span><span>from</span> <span>fastapi.responses</span> <span>import</span> <span>RedirectResponse</span><span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span><span>import</span> <span>secrets</span><span>from</span> <span>fastapi</span> <span>import</span> <span>FastAPI</span><span>,</span> <span>HTTPException</span> <span>from</span> <span>fastapi.middleware.cors</span> <span>import</span> <span>CORSMiddleware</span> <span>from</span> <span>fastapi.responses</span> <span>import</span> <span>RedirectResponse</span> <span>from</span> <span>pydantic</span> <span>import</span> <span>BaseModel</span> <span>import</span> <span>secrets</span>from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import RedirectResponse from pydantic import BaseModel import secrets
Enter fullscreen mode Exit fullscreen mode
Next, create a FastAPI instance:
<span>app</span> <span>=</span> <span>FastAPI</span><span>()</span><span>app</span> <span>=</span> <span>FastAPI</span><span>()</span>app = FastAPI()
Enter fullscreen mode Exit fullscreen mode
Next, define a Pydantic model for the request payload:
<span>class</span> <span>URLItem</span><span>(</span><span>BaseModel</span><span>):</span><span>original_url</span><span>:</span> <span>str</span><span>class</span> <span>URLItem</span><span>(</span><span>BaseModel</span><span>):</span> <span>original_url</span><span>:</span> <span>str</span>class URLItem(BaseModel): original_url: str
Enter fullscreen mode Exit fullscreen mode
Next, we’ll use the in-memory database to store the mapping between the short URL and the original URL
<span>url_database</span> <span>=</span> <span>{}</span><span>url_database</span> <span>=</span> <span>{}</span>url_database = {}
Enter fullscreen mode Exit fullscreen mode
Next, let’s configure the CORS middleware
<span>origins</span> <span>=</span> <span>[</span><span>"</span><span>*</span><span>"</span><span>]</span><span>app</span><span>.</span><span>add_middleware</span><span>(</span><span>CORSMiddleware</span><span>,</span><span>allow_origins</span><span>=</span><span>origins</span><span>,</span><span>allow_credentials</span><span>=</span><span>True</span><span>,</span><span>allow_methods</span><span>=</span><span>[</span><span>"</span><span>*</span><span>"</span><span>],</span><span>allow_headers</span><span>=</span><span>[</span><span>"</span><span>*</span><span>"</span><span>],</span><span>)</span><span>origins</span> <span>=</span> <span>[</span><span>"</span><span>*</span><span>"</span><span>]</span> <span>app</span><span>.</span><span>add_middleware</span><span>(</span> <span>CORSMiddleware</span><span>,</span> <span>allow_origins</span><span>=</span><span>origins</span><span>,</span> <span>allow_credentials</span><span>=</span><span>True</span><span>,</span> <span>allow_methods</span><span>=</span><span>[</span><span>"</span><span>*</span><span>"</span><span>],</span> <span>allow_headers</span><span>=</span><span>[</span><span>"</span><span>*</span><span>"</span><span>],</span> <span>)</span>origins = ["*"] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], )
Enter fullscreen mode Exit fullscreen mode
Next, let’s create an endpoint for shortening a URL
<span>@app.post</span><span>(</span><span>"</span><span>/shorten/</span><span>"</span><span>)</span><span>def</span> <span>shorten_url</span><span>(</span><span>url_item</span><span>:</span> <span>URLItem</span><span>):</span><span># Generate a short URL using secrets module </span> <span>short_url</span> <span>=</span> <span>secrets</span><span>.</span><span>token_urlsafe</span><span>(</span><span>6</span><span>)</span><span># Store the mapping between short URL and original URL in the database </span> <span>url_database</span><span>[</span><span>short_url</span><span>]</span> <span>=</span> <span>url_item</span><span>.</span><span>original_url</span><span># Return the short URL in the response </span> <span>return</span> <span>{</span><span>"</span><span>short_url</span><span>"</span><span>:</span> <span>short_url</span><span>}</span><span>@app.post</span><span>(</span><span>"</span><span>/shorten/</span><span>"</span><span>)</span> <span>def</span> <span>shorten_url</span><span>(</span><span>url_item</span><span>:</span> <span>URLItem</span><span>):</span> <span># Generate a short URL using secrets module </span> <span>short_url</span> <span>=</span> <span>secrets</span><span>.</span><span>token_urlsafe</span><span>(</span><span>6</span><span>)</span> <span># Store the mapping between short URL and original URL in the database </span> <span>url_database</span><span>[</span><span>short_url</span><span>]</span> <span>=</span> <span>url_item</span><span>.</span><span>original_url</span> <span># Return the short URL in the response </span> <span>return</span> <span>{</span><span>"</span><span>short_url</span><span>"</span><span>:</span> <span>short_url</span><span>}</span>@app.post("/shorten/") def shorten_url(url_item: URLItem): # Generate a short URL using secrets module short_url = secrets.token_urlsafe(6) # Store the mapping between short URL and original URL in the database url_database[short_url] = url_item.original_url # Return the short URL in the response return {"short_url": short_url}
Enter fullscreen mode Exit fullscreen mode
Finally, create an endpoint for redirecting to the original URL based on the short URL
<span>@app.get</span><span>(</span><span>"</span><span>/{short_url}</span><span>"</span><span>)</span><span>def</span> <span>redirect_to_original</span><span>(</span><span>short_url</span><span>:</span> <span>str</span><span>):</span><span># Retrieve the original URL from the database </span> <span>original_url</span> <span>=</span> <span>url_database</span><span>.</span><span>get</span><span>(</span><span>short_url</span><span>)</span><span># If the original URL exists, redirect to it </span> <span>if</span> <span>original_url</span><span>:</span><span>return</span> <span>RedirectResponse</span><span>(</span><span>url</span><span>=</span><span>original_url</span><span>)</span><span>else</span><span>:</span><span># If the short URL is not found in the database, return an error response </span> <span>return</span> <span>{</span><span>"</span><span>error</span><span>"</span><span>:</span> <span>"</span><span>URL not found</span><span>"</span><span>}</span><span>@app.get</span><span>(</span><span>"</span><span>/{short_url}</span><span>"</span><span>)</span> <span>def</span> <span>redirect_to_original</span><span>(</span><span>short_url</span><span>:</span> <span>str</span><span>):</span> <span># Retrieve the original URL from the database </span> <span>original_url</span> <span>=</span> <span>url_database</span><span>.</span><span>get</span><span>(</span><span>short_url</span><span>)</span> <span># If the original URL exists, redirect to it </span> <span>if</span> <span>original_url</span><span>:</span> <span>return</span> <span>RedirectResponse</span><span>(</span><span>url</span><span>=</span><span>original_url</span><span>)</span> <span>else</span><span>:</span> <span># If the short URL is not found in the database, return an error response </span> <span>return</span> <span>{</span><span>"</span><span>error</span><span>"</span><span>:</span> <span>"</span><span>URL not found</span><span>"</span><span>}</span>@app.get("/{short_url}") def redirect_to_original(short_url: str): # Retrieve the original URL from the database original_url = url_database.get(short_url) # If the original URL exists, redirect to it if original_url: return RedirectResponse(url=original_url) else: # If the short URL is not found in the database, return an error response return {"error": "URL not found"}
Enter fullscreen mode Exit fullscreen mode
Run uvicorn main:app --reload
to run the backend
Frontend with ReactJS
Create a Vite project and install dependencies including axios
npm init vite@latest url-shortener <span>--template</span> react<span>cd </span>url-shortenernpm <span>install</span> <span>&&</span> npm <span>install </span>axiosnpm init vite@latest url-shortener <span>--template</span> react <span>cd </span>url-shortener npm <span>install</span> <span>&&</span> npm <span>install </span>axiosnpm init vite@latest url-shortener --template react cd url-shortener npm install && npm install axios
Enter fullscreen mode Exit fullscreen mode
Import the useState
hook from React to manage the components state and the axios
library to make HTTP Requests
<span>import</span> <span>{</span> <span>useState</span> <span>}</span> <span>from</span> <span>'</span><span>react</span><span>'</span><span>;</span><span>import</span> <span>axios</span> <span>from</span> <span>'</span><span>axios</span><span>'</span><span>;</span><span>import</span> <span>{</span> <span>useState</span> <span>}</span> <span>from</span> <span>'</span><span>react</span><span>'</span><span>;</span> <span>import</span> <span>axios</span> <span>from</span> <span>'</span><span>axios</span><span>'</span><span>;</span>import { useState } from 'react'; import axios from 'axios';
Enter fullscreen mode Exit fullscreen mode
The component uses the useState
hook to manage state variables for the original URL (originalUrl
), the shortened URL (shortUrl
), and a loading indicator (loading
).
<span>const</span> <span>ShortenerForm</span> <span>=</span> <span>()</span> <span>=></span> <span>{</span><span>// State variables for the original URL, short URL, and loading state</span><span>const</span> <span>[</span><span>originalUrl</span><span>,</span> <span>setOriginalUrl</span><span>]</span> <span>=</span> <span>useState</span><span>(</span><span>''</span><span>);</span><span>const</span> <span>[</span><span>shortUrl</span><span>,</span> <span>setShortUrl</span><span>]</span> <span>=</span> <span>useState</span><span>(</span><span>''</span><span>);</span><span>const</span> <span>[</span><span>loading</span><span>,</span> <span>setLoading</span><span>]</span> <span>=</span> <span>useState</span><span>(</span><span>false</span><span>);</span><span>const</span> <span>ShortenerForm</span> <span>=</span> <span>()</span> <span>=></span> <span>{</span> <span>// State variables for the original URL, short URL, and loading state</span> <span>const</span> <span>[</span><span>originalUrl</span><span>,</span> <span>setOriginalUrl</span><span>]</span> <span>=</span> <span>useState</span><span>(</span><span>''</span><span>);</span> <span>const</span> <span>[</span><span>shortUrl</span><span>,</span> <span>setShortUrl</span><span>]</span> <span>=</span> <span>useState</span><span>(</span><span>''</span><span>);</span> <span>const</span> <span>[</span><span>loading</span><span>,</span> <span>setLoading</span><span>]</span> <span>=</span> <span>useState</span><span>(</span><span>false</span><span>);</span>const ShortenerForm = () => { // State variables for the original URL, short URL, and loading state const [originalUrl, setOriginalUrl] = useState(''); const [shortUrl, setShortUrl] = useState(''); const [loading, setLoading] = useState(false);
Enter fullscreen mode Exit fullscreen mode
This function is called when the user clicks the “Shorten URL” button. It first checks if the original URL is provided and shows an alert if not. If the URL is valid, it sets the loading state to true
, sends a POST request to the specified URL shortening service, and updates the state with the shortened URL. Any errors that occur during the process are logged, and the loading state is reset to false
regardless of success or failure.
<span>const</span> <span>shortenUrl</span> <span>=</span> <span>async </span><span>()</span> <span>=></span> <span>{</span><span>if </span><span>(</span><span>!</span><span>originalUrl</span><span>)</span> <span>{</span><span>alert</span><span>(</span><span>'</span><span>Please enter a URL.</span><span>'</span><span>);</span><span>return</span><span>;</span><span>}</span><span>setLoading</span><span>(</span><span>true</span><span>);</span><span>try</span> <span>{</span><span>// Making a POST request to a URL shortening service</span><span>const</span> <span>response</span> <span>=</span> <span>await</span> <span>axios</span><span>.</span><span>post</span><span>(</span><span>'</span><span>http://localhost:8000/shorten/</span><span>'</span><span>,</span> <span>{</span><span>original_url</span><span>:</span> <span>originalUrl</span><span>,</span><span>});</span><span>// Updating state with the shortened URL</span><span>setShortUrl</span><span>(</span><span>`http://localhost:8000/</span><span>${</span><span>response</span><span>.</span><span>data</span><span>.</span><span>short_url</span><span>}</span><span>`</span><span>);</span><span>}</span> <span>catch </span><span>(</span><span>error</span><span>)</span> <span>{</span><span>console</span><span>.</span><span>error</span><span>(</span><span>'</span><span>Error shortening URL:</span><span>'</span><span>,</span> <span>error</span><span>);</span><span>}</span> <span>finally</span> <span>{</span><span>setLoading</span><span>(</span><span>false</span><span>);</span><span>}</span><span>};</span><span>const</span> <span>shortenUrl</span> <span>=</span> <span>async </span><span>()</span> <span>=></span> <span>{</span> <span>if </span><span>(</span><span>!</span><span>originalUrl</span><span>)</span> <span>{</span> <span>alert</span><span>(</span><span>'</span><span>Please enter a URL.</span><span>'</span><span>);</span> <span>return</span><span>;</span> <span>}</span> <span>setLoading</span><span>(</span><span>true</span><span>);</span> <span>try</span> <span>{</span> <span>// Making a POST request to a URL shortening service</span> <span>const</span> <span>response</span> <span>=</span> <span>await</span> <span>axios</span><span>.</span><span>post</span><span>(</span><span>'</span><span>http://localhost:8000/shorten/</span><span>'</span><span>,</span> <span>{</span> <span>original_url</span><span>:</span> <span>originalUrl</span><span>,</span> <span>});</span> <span>// Updating state with the shortened URL</span> <span>setShortUrl</span><span>(</span><span>`http://localhost:8000/</span><span>${</span><span>response</span><span>.</span><span>data</span><span>.</span><span>short_url</span><span>}</span><span>`</span><span>);</span> <span>}</span> <span>catch </span><span>(</span><span>error</span><span>)</span> <span>{</span> <span>console</span><span>.</span><span>error</span><span>(</span><span>'</span><span>Error shortening URL:</span><span>'</span><span>,</span> <span>error</span><span>);</span> <span>}</span> <span>finally</span> <span>{</span> <span>setLoading</span><span>(</span><span>false</span><span>);</span> <span>}</span> <span>};</span>const shortenUrl = async () => { if (!originalUrl) { alert('Please enter a URL.'); return; } setLoading(true); try { // Making a POST request to a URL shortening service const response = await axios.post('http://localhost:8000/shorten/', { original_url: originalUrl, }); // Updating state with the shortened URL setShortUrl(`http://localhost:8000/${response.data.short_url}`); } catch (error) { console.error('Error shortening URL:', error); } finally { setLoading(false); } };
Enter fullscreen mode Exit fullscreen mode
The render method returns JSX that defines the component’s UI. It includes a form with an input field for the original URL, a button to trigger URL shortening, and a display area for the shortened URL.
<span>return </span><span>(</span><span><</span><span>div</span> <span>className</span><span>=</span><span>"</span><span>flex items-center justify-center h-screen</span><span>"</span><span>></span><span><</span><span>div</span> <span>className</span><span>=</span><span>"</span><span>bg-gray-100 p-6 rounded shadow-md w-96</span><span>"</span><span>></span><span><</span><span>h1</span> <span>className</span><span>=</span><span>"</span><span>text-2xl font-semibold mb-4</span><span>"</span><span>></span><span>URL</span> <span>Shortener</span><span><</span><span>/h1</span><span>> </span> <span><</span><span>input</span><span>className</span><span>=</span><span>"</span><span>w-full border p-2 mb-4</span><span>"</span><span>type</span><span>=</span><span>"</span><span>text</span><span>"</span><span>value</span><span>=</span><span>{</span><span>originalUrl</span><span>}</span><span>onChange</span><span>=</span><span>{(</span><span>e</span><span>)</span> <span>=></span> <span>setOriginalUrl</span><span>(</span><span>e</span><span>.</span><span>target</span><span>.</span><span>value</span><span>)}</span><span>placeholder</span><span>=</span><span>"</span><span>Enter URL to shorten</span><span>"</span><span>/></span><span><</span><span>button</span><span>className</span><span>=</span><span>{</span><span>`bg-blue-500 text-white px-4 py-2 rounded </span><span>${</span><span>loading</span> <span>?</span> <span>'</span><span>opacity-50 cursor-not-allowed</span><span>'</span> <span>:</span> <span>''</span><span>}</span><span>`</span><span>}</span><span>onClick</span><span>=</span><span>{</span><span>shortenUrl</span><span>}</span><span>disabled</span><span>=</span><span>{</span><span>loading</span><span>}</span><span>></span><span>{</span><span>loading</span> <span>?</span> <span>'</span><span>Loading...</span><span>'</span> <span>:</span> <span>'</span><span>Shorten URL</span><span>'</span><span>}</span><span><</span><span>/button</span><span>> </span> <span>{</span><span>shortUrl</span> <span>&&</span> <span>(</span><span><</span><span>div</span> <span>className</span><span>=</span><span>"</span><span>mt-4</span><span>"</span><span>></span><span><</span><span>p</span> <span>className</span><span>=</span><span>"</span><span>font-semibold</span><span>"</span><span>></span><span>Shortened</span> <span>URL</span><span>:</span><span><</span><span>/p</span><span>> </span> <span><</span><span>a</span><span>href</span><span>=</span><span>{</span><span>shortUrl</span><span>}</span><span>target</span><span>=</span><span>"</span><span>_blank</span><span>"</span><span>rel</span><span>=</span><span>"</span><span>noopener noreferrer</span><span>"</span><span>className</span><span>=</span><span>"</span><span>text-blue-500 hover:underline</span><span>"</span><span>></span><span>{</span><span>shortUrl</span><span>}</span><span><</span><span>/a</span><span>> </span> <span><</span><span>/div</span><span>> </span> <span>)}</span><span><</span><span>/div</span><span>> </span> <span><</span><span>/div</span><span>> </span> <span>);</span><span>};</span><span>return </span><span>(</span> <span><</span><span>div</span> <span>className</span><span>=</span><span>"</span><span>flex items-center justify-center h-screen</span><span>"</span><span>></span> <span><</span><span>div</span> <span>className</span><span>=</span><span>"</span><span>bg-gray-100 p-6 rounded shadow-md w-96</span><span>"</span><span>></span> <span><</span><span>h1</span> <span>className</span><span>=</span><span>"</span><span>text-2xl font-semibold mb-4</span><span>"</span><span>></span><span>URL</span> <span>Shortener</span><span><</span><span>/h1</span><span>> </span> <span><</span><span>input</span> <span>className</span><span>=</span><span>"</span><span>w-full border p-2 mb-4</span><span>"</span> <span>type</span><span>=</span><span>"</span><span>text</span><span>"</span> <span>value</span><span>=</span><span>{</span><span>originalUrl</span><span>}</span> <span>onChange</span><span>=</span><span>{(</span><span>e</span><span>)</span> <span>=></span> <span>setOriginalUrl</span><span>(</span><span>e</span><span>.</span><span>target</span><span>.</span><span>value</span><span>)}</span> <span>placeholder</span><span>=</span><span>"</span><span>Enter URL to shorten</span><span>"</span> <span>/></span> <span><</span><span>button</span> <span>className</span><span>=</span><span>{</span><span>`bg-blue-500 text-white px-4 py-2 rounded </span><span>${</span><span>loading</span> <span>?</span> <span>'</span><span>opacity-50 cursor-not-allowed</span><span>'</span> <span>:</span> <span>''</span><span>}</span><span>`</span><span>}</span> <span>onClick</span><span>=</span><span>{</span><span>shortenUrl</span><span>}</span> <span>disabled</span><span>=</span><span>{</span><span>loading</span><span>}</span> <span>></span> <span>{</span><span>loading</span> <span>?</span> <span>'</span><span>Loading...</span><span>'</span> <span>:</span> <span>'</span><span>Shorten URL</span><span>'</span><span>}</span> <span><</span><span>/button</span><span>> </span> <span>{</span><span>shortUrl</span> <span>&&</span> <span>(</span> <span><</span><span>div</span> <span>className</span><span>=</span><span>"</span><span>mt-4</span><span>"</span><span>></span> <span><</span><span>p</span> <span>className</span><span>=</span><span>"</span><span>font-semibold</span><span>"</span><span>></span><span>Shortened</span> <span>URL</span><span>:</span><span><</span><span>/p</span><span>> </span> <span><</span><span>a</span> <span>href</span><span>=</span><span>{</span><span>shortUrl</span><span>}</span> <span>target</span><span>=</span><span>"</span><span>_blank</span><span>"</span> <span>rel</span><span>=</span><span>"</span><span>noopener noreferrer</span><span>"</span> <span>className</span><span>=</span><span>"</span><span>text-blue-500 hover:underline</span><span>"</span> <span>></span> <span>{</span><span>shortUrl</span><span>}</span> <span><</span><span>/a</span><span>> </span> <span><</span><span>/div</span><span>> </span> <span>)}</span> <span><</span><span>/div</span><span>> </span> <span><</span><span>/div</span><span>> </span> <span>);</span> <span>};</span>return ( <div className="flex items-center justify-center h-screen"> <div className="bg-gray-100 p-6 rounded shadow-md w-96"> <h1 className="text-2xl font-semibold mb-4">URL Shortener</h1> <input className="w-full border p-2 mb-4" type="text" value={originalUrl} onChange={(e) => setOriginalUrl(e.target.value)} placeholder="Enter URL to shorten" /> <button className={`bg-blue-500 text-white px-4 py-2 rounded ${loading ? 'opacity-50 cursor-not-allowed' : ''}`} onClick={shortenUrl} disabled={loading} > {loading ? 'Loading...' : 'Shorten URL'} </button> {shortUrl && ( <div className="mt-4"> <p className="font-semibold">Shortened URL:</p> <a href={shortUrl} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline" > {shortUrl} </a> </div> )} </div> </div> ); };
Enter fullscreen mode Exit fullscreen mode
The component is exported as the default export, making it available for use in other parts of the application.
<span>export</span> <span>default</span> <span>ShortenerForm</span><span>;</span><span>export</span> <span>default</span> <span>ShortenerForm</span><span>;</span>export default ShortenerForm;
Enter fullscreen mode Exit fullscreen mode
Now run npm run dev
to see the frontend
In summary, this React component provides a simple form for users to enter a URL, click a button to shorten it using a specified service, and then displays the shortened URL. The loading state is used to provide feedback to the user during the URL shortening process.
Full code can be seen at https://github.com/reyesvicente/urlshortener and the site can be seen at https://urlshrtnr.vercel.app/
原文链接:Creating a URL Shortener with FastAPI, ReactJs and TailwindCSS
暂无评论内容