A URL shortener made by Dennis Hodges.
- Clone this repo.
git clone https://github.com/fermentationist/urly.git
(Or, if you prefer, download the zip.) cd
into project root.cd urly
.- Type
npm install
. This will install dependencies. (If you don't have Node.js installed, you can download it here.) - To build and run app, type
npm start
. (Or, to run dev server, typenpm run dev
.) - After build is complete, open localhost:4000 in your browser. (Or, if running dev server, go to localhost:3000 instead.)
- To run API and unit tests, type
npm test
.
- Data Storage:
Implementation: For the sake of simplicity and portability (I didn't want the user to have to configure a local env among other things), I chose a SQLite database. A simple table, named redirects, was used:
Scaling: While a SQLite database is not a very scalable storage solution, a more robust SQL database would probably still be an appropriate choice to store redirects. (There are also solutions like Vitess and services like Planetscale that can horizontally scale a MySQL database.) A relational table fits the data as well or better than a document-based storage solution, in my opinion.
CREATE TABLE IF NOT EXISTS redirects( id INTEGER PRIMARY KEY NOT NULL, url TEXT NOT NULL UNIQUE );
- URL Generation:
Implementation: Short (7 or 8 chars), unique alphanumeric ids are generated by converting the current Unix timestamp in milliseconds to a base62 string. They are appended to the host URL to create the shortened link. These ids will be unique as long as they are created at least 1ms apart (a rate limiter is in place to guarantee this). The integer-based timestamp value used to construct the id is used as a primary key in the database, instead of the base62 version, for faster indexing.
Scaling: I chose this solution for ease of implementation, but its limitations mean that a different solution will be needed at scale. For instance, the uniqueness of the ids is dependent on generating no more than one in any given millisecond. Currently, a rate limiter is being used to prevent this, but guaranteeing uniqueness will be more difficult if there are multiple servers.
Some other possible solutions: A true UUID can be used, but it will result in a significantly longer URL, which we probably don't want. A better solution might be to create a database table full of pre-generated ids, each already known to be unique (allowing them to remain short - for example, there are 62^6, or over 56 billion possible 6-character alphanumeric combinations, if using upper and lower case letters). A single table could be used to avoid joins. The table would look like this:
To add a url would take three queries (current implementation requires two, one
CREATE TABLE IF NOT EXISTS redirects( integer_id BIGINT PRIMARY KEY NOT NULL, short_id VARCHAR(6) NOT NULL UNIQUE, url VARCHAR(2048) DEFAULT NULL );
SELECT
and oneINSERT
): First, to check if the URL is already in the database:SELECT short_id FROM redirects WHERE url=?
Then another, to get the ids:SELECT short_id, integer_id FROM redirects WHERE url IS NULL LIMIT 1;
and then:UPDATE redirects SET url=? WHERE integer_id=?
- Server:
Implementation: I have used a Node.js (Express) server, both because it is well-suited to the task, and because of my preexisting familiarity with it, making it a fast and easy option. It is also easier to consolidate into a single repo with the React front end than it would be using a Python framework. The API consists of three endpoints:
POST /api/shorten-url
,GET /api/test
andGET /:redirectID
. The first does the work of returning a shortened URL, the second is just for testing, and then the redirect path for shortened URLs. Scaling: Node.js is asynchronous and capable of handling up to ~15K requests/second, making it a decent choice as the project scales. Of course, a load balancer would be needed once multiple servers become necessary. - User Interface: Implementation: The front end is built with React, largely because I have a lot of familiarity with it, making it fast and easy to develop with. The app works as I intended and is fairly fast, but there are probably other, more lightweight front end solutions that could have been used (maybe Svelte or vanilla JS), especially given the simplicity of the app. Scaling: The choice of client is not a concern of scale to the same degree that decisions around the back end are. This SPA should work fine at scale, though it should perhaps first be optimized for performance and undergo some design refinement.