There are now numerous short URL services (also known as URL shorteners) on the Internet – but what exactly do they do, and why do we actually need them? As the name suggests, these services provide a short URL for any web address. What this means is that you can submit any web address to the service provider and they will give you back a shortened version of it.
This is particularly useful in allowing you to send friends or relatives a short link instead of an extremely long one. Many social media outlets also limit the number of characters per message, and these short URL services enjoy an important role here too as a way of getting around certain limits. The most commonly used short URL service in the world at present is the US-run Bit.ly.

Below I will show you how easily you can build a service like this for yourself using the CodeIgniter framework:

HOW DOES A SHORT URL SERVICE WORK IN PRACTICE?

Virtually all short URL providers have services roughly based on the same principle:

On the provider’s home page there is usually a text field where you can enter your original URL, from which a unique short URL is then generated. Anyone who attempts to visit this short URL is then instantly redirected to the respective destination address – i.e. the address which was entered when generating the URL. In addition to this, many providers also supply statistics about access etc.

CODEIGNITER SETUP

In the following section, I will show you how the CodeIgniter setup works.

Prepare the framework

In order to get started with our short URL service, we first need a foundation on which we can build.
To do so, download the latest version of the framework from the CodeIgniter homepage. This is currently version 3.1.5.

Next, copy all the files, ideally to the subfolder “ci-urlshortener” on the web server (e.g. XAMPP, LAMP, etc.), and open its root directory in your browser, e.g. http://localhost/ci-urlshortener/.

Once all of the requirements for the framework are met, you should then see the CodeIgniter welcome page.

To ensure that the speaking URLs can also be accessed correctly, create a .htaccess file with the following content:

1

2

3

4

RewriteEngine on

RewriteRule^(assets)($|/)-[L]

RewriteRule.*index.php

If necessary, an optional environment variable can be specified here to distinguish between a development system (“development”), test system (“testing”) or production system (“production”).
This would look as follows:

1

SetEnv CI_ENV development

Next, open /application/config/autoload.php (the autoloader configuration file) and add the URL and form helper along with the database library. We will need these components later.

PHP

1

2

$autoload['libraries']=array('database');

$autoload['helper']=array('url','form');

In /application/config/routes.php (the route configuration file), we now need to change the default controller from “welcome” to “home” since we want our URL shortener application to be loaded by default later.

We will also define two additional routes here: one for redirects (as a catch-all) and one for statistics.

PHP

1

2

3

4

5

6

$route['default_controller']='home';

$route['404_override']='';

$route['translate_uri_dashes']=FALSE;

$route['(:any)/stats']='home/stats/$1';

$route['(:any)']='home/redirect/$1';

The redirect route (catch-all) is deliberately defined last, otherwise it would intercept all other routes and so routing would not work properly.

CREATE INDEX PAGE AND STORE URLS

Having referred to a “home” controller in the route configuration, we also need to create one. To do so, navigate to /application/controller and create a file named Home.php (case-sensitive!).
In this file we now need to create the basic framework of 3 actions: one for the index page, one for the redirect itself, and one for the statistics.

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

<?php

defined('BASEPATH')ORexit('No direct script access allowed');

classHomeextendsCI_Controller{

publicfunction__construct()

{

parent::__construct();

}

publicfunctionindex()

{

}

publicfunctionredirect($alias)

{

}

publicfunctionstats($alias)

{

}

}

Later on, we will load our models in the constructor above to avoid having to do this separately for every single action.

Now we have our controller defined along with the necessary actions.Next we create our database tables as follows:

MySQL

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

CREATETABLE`urls`(

`id`INTNOT NULLAUTO_INCREMENT,

`url`VARCHAR(255)NOT NULL,

`alias`VARCHAR(100)NOT NULL,

`created`DATETIMENOT NULL,

PRIMARY KEY(`id`),

INDEX(`alias`)

)ENGINE=InnoDB;

CREATETABLE`statistics`(

`id`INTNOT NULLAUTO_INCREMENT,

`url_id`INTNOT NULL,

`created`DATETIMENOT NULL,

PRIMARY KEY(`id`),

INDEX(`url_id`)

)ENGINE=InnoDB;

This gives us one table for redirects (storing URLs with associated aliases) and one for statistics (logging each time a particular URL is accessed).

Next we will create a model for the URL table. To do so, navigate to /application/models and create a new file named Url_model.php.

We need the following functions here: the ability to add a new URL (with add_url()) and to retrieve a URL by ID (with get_url_by_id()) or alias (with get_url()).

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

<?php

defined('BASEPATH')ORexit('No direct script access allowed');

classUrl_modelextendsCI_Model{

functionadd_url($url)

{

// build up data array

$data=array(

'url'=>(string)$url,

'alias'=>(string)uniqid(),// creates a random key

'created'=>date('Y-m-d H:i:s'),

);

// inserts the data into database

$this->db->insert('urls',$data);

// return this ID of the new inserted record

return$this->db->insert_id();

}

publicfunctionget_url_by_id($id)

{

$this->db->select('*');

$this->db->from('urls');

$this->db->where('id',(int)$id);

$result=$this->db->get()->row_object();

// check if the requested record was found

if(count($result)>0){

return$result;

}else{

returnFALSE;

}

}

publicfunctionget_url($alias)

{

$this->db->select('*');

$this->db->from('urls');

$this->db->where('alias',(string)$alias);

$result=$this->db->get()->row_object();

// check if the requested record was found

if(count($result)>0){

return$result;

}else{

returnFALSE;

}

}

}

The get_url_by_id() and get_url() functions are identical except for retrieving different database fields.

Once we have our primary model for the URLs, we load it as follows in the constructor for our home controller:

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

<?php

defined('BASEPATH')ORexit('No direct script access allowed');

classHomeextendsCI_Controller{

publicfunction__construct()

{

parent::__construct();

// load models

$this->load->model('Url_model');

}

Now we can tackle the next step: creating our index page.

To do so, we will define a data array with a few default values, and define logic for what should happen when a POST request arrives – in our case, a new URL should be created here with an alias:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

publicfunctionindex()

{

$data=array(

'error'=>false,

'show_details'=>false,

);

$post=$this->input->post(NULL,TRUE);

// check if request method was 'post' - if yes, then try to create short url

if($post){

$url=$post['url'];

// validate url

if(filter_var($url,FILTER_VALIDATE_URL)){

$id=$this->Url_model->add_url($url);

$url_data=$this->Url_model->get_url_by_id($id);

$data['show_details']=true;

$data['url_data']=$url_data;

}else{

$data['error']="Invalid URL!";

}

}

// load view and assign data array

$this->load->view('home',$data);

}

In this example code, there is an additional PHP validation which checks that the URL submitted by the user is in fact a valid URL.

If not, there could be unexpected side-effects during the redirect itself.

Once the new URL has been stored in the database, this record is retrieved from there and passed to our view – after all, we want to show the user what their alias for the submitted URL looks like.

In order to do this, however, we still need the associated home view (based on the Bootstrap framework) in a file named /application/views/home.php:

The linked bootstrap_flatly.min.css file is the free “Flatly” Bootstrap theme from Bootswatch.
This CSS theme is optional and can therefore be left out if you prefer.

In the style.css file, we will define only a few basic styles:

CSS

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

body {

padding-top:50px;

}

.intro {

padding:40px15px;

text-align:center;

}

.intro h1 {

margin-bottom:40px;

}

.url_data {

margin-top:60px;

text-align:center;

}

.url_data h2 {

font-size:25px;

}

The home view itself merely provides a form to submit a URL, and an area below to display the generated alias.

Now we can test our application for the first time by entering any URL into the form and submitting it.
We should now be able to find this URL in the database along with an alias, which should be displayed to us on the index page underneath the form.

CONFIGURE REDIRECT

Because we also want to analyze the requests received by our system instead of simply redirecting to the destination URL, we will now create a model for the statistics table. To do so, navigate to /application/models and create a new file named Statistics_model.php with the following content:

The functions here are limited to creating a new entry for a particular URL (with add_log()), and retrieving all entries for a URL (with get_logs()).

In the latter case, entries are grouped by date and the total number of times the URL was accessed per day is returned as a sum.

Load this model in the home controller constructor in the same way as the URL model:

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<?php

defined('BASEPATH')ORexit('No direct script access allowed');

classHomeextendsCI_Controller{

publicfunction__construct()

{

parent::__construct();

// load models

$this->load->model('Url_model');

$this->load->model('Statistics_model');

}

Having defined an empty “redirect” action earlier, it is now time to give this a function too.

The parameter passed on as $alias is the one that was previously generated when the URL was stored.

This is now used to search the database for the desired entry with the help of the get_url() function we created before.

PHP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

publicfunctionredirect($alias)

{

$url_data=$this->Url_model->get_url($alias);

// check if there's an url with this alias

if(!$url_data){

header("HTTP/1.0 404 Not Found");

$this->load->view('not_found');

}else{

$this->Statistics_model->add_log($url_data->id);

header('Location: '.$url_data->url,true,302);

exit();

}

}

When the appropriate database entry has been found, we can carry out the actual redirect using the location header.

(The “302” at the end indicates the accompanying HTTP status code. Of course, this would usually be a “301” (preferable in production systems from an SEO perspective) – but since redirects with a 301 header are cached by the browser, this would be highly impractical for testing, so in this case it is “302”.)

Before this redirect, however, we still want to our statistics model to store a record of access to the URL.

It is therefore important that there is no other output after the location header (or generally after a header output) – so we will add an exit() afterwards as a precaution.
In the event that no database entry is found for an alias, we simply refer to a “not found” view:

This view is stored in a file named /application/views/not_found.php. It can be customized, and in our case it has no particular function.

The short URL previously generated in the last test can now be tested – if we have done everything correctly, it will redirect to the destination URL and an access log entry will be stored in the statistics table.

SHOW STATISTICS

At first glance, the “stats” action looks like the “redirect” action. This is due to them working in a similar way: both carry out a database search using a transferred alias.

The “redirect” action results in a redirect, and the “stats” action uses this data to retrieve all existing log entries for this URL.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

publicfunctionstats($alias)

{

$url_data=$this->Url_model->get_url($alias);

// check if there's an url with this alias

if(!$url_data){

header("HTTP/1.0 404 Not Found");

$this->load->view('not_found');

}else{

$logs=$this->Statistics_model->get_logs($url_data->id);

$data=array(

'url_data'=>$url_data,

'logs'=>$logs,

);

$this->load->view('stats',$data);

}

}

Once again, our “not found” view from above is used in the event that no database entry is found for a particular alias.

The associated view (/application/views/stats.php) would then look as follows:

We iterate over all existing log entries for it in a foreach loop and show them in table format.

If preferred, this data can also be displayed as a diagram. There are numerous jQuery plugins for this on the Internet.

In the event that there are no log entries (if, for example, the short URL has never been accessed), a note to this effect will be shown.

CONCLUSION

Short URL services are very simple in structure. Their use is not always simple, however, and they can also be misused since end users cannot clearly see where they will be redirected.

Although some major providers already have quite good protection against malware and phishing, security remains controversial despite its relatively high standards.

The possible tracking option (limited in this tutorial to logging access) can also be expanded at will – e.g. to include capturing a user’s IP (if possible, it would be advisable for privacy reasons not to store the last part of the IP or to anonymize it), and as a result their country (using GeoIP) or any referrers, etc.

The analysis of such data is especially important in marketing, as this allows advertising campaigns etc. to be targeted more specifically and made more cost-effective. For this reason, these short URL services are of particular interest to businesses. Short URLs of this kind are used in things like newsletters, for example, to find out how often a link was clicked. 😉

Anexia is

ANEXIA offers quality, customized solutions for web and managed hosting as well as individual software and app development. The company was founded in 2006 in Klagenfurt, Austria, and now has additional offices in Vienna, Munich, Cologne and New York City to serve numerous international customers.