★ A beautiful webapp to fetch dns records
Tags: dns, php, laravel
Published: 2020-01-02 22:49:50

---
title: ★ A beautiful webapp to fetch dns records
published: true
date: 2017-11-06 06:45:41 UTC
tags: dns, php, laravel
canonical_url: https://freek.dev/893-a-beautiful-webapp-to-fetch-dns-records
---

Recently [my company Spatie](https://spatie.be) launched [https://dnsrecords.io](https://dnsrecords.io), a beautiful site to quickly lookup dns records.

 ![](https://freek.dev/uploads/2017/11/Screen-Shot-2017-11-04-at-10.31.58.png)

True to form, we also opensourced it, here is the sourcecode [on GitHub](https://github.com/spatie/dnsrecords.io). If you want to do some dns lookups in your own app, you'll be happy to know that we extracted the dns lookup functionalities to [a package](https://github.com/spatie/dns).

In this blog post I'd like to share why and how we've built all this.

### Why create another dns lookup service?

A few weeks ago [Jef](https://twitter.com/vdv_jef), our project manager at [Spatie](https://spatie.be), was asked by a client to give some dns related info. Because Jef is not a technical person. He has a fear/hate relation with the terminal. So he always delegates technical questions like those to his teammates. They just use [`dig`](http://www.thegeekstuff.com/2012/02/dig-command-examples/) to quickly get dns records. Wouldn't it be great if Jef could do the dns lookups on his own? An idea was born.

But aren't there already many services to perform dns lookups? Let's [Google around] (https://www.google.be/search?q=dns+records+lookup&oq=dns+records+lookup). Here are a few of the first hits:

 ![](https://freek.dev/uploads/2017/11/Screen-Shot-2017-11-04-at-10.41.16.png) ![](https://freek.dev/uploads/2017/11/Screen-Shot-2017-11-04-at-10.39.42.png) ![](https://freek.dev/uploads/2017/11/Screen-Shot-2017-11-04-at-10.44.36.png)

Most of the services work, but they are really really ugly. We couldn't find any dns lookup service that looks beautiful. So we went ahead with creating a webapp of our own.

### Introducing dnsrecords.io

My colleague [Willem](https://twitter.com/willemvbockstal) did an excellent job in making [dnsrecords.io](https://dnsrecords.io) look beautiful. This is what you see when visiting the site.

 ![](https://freek.dev/uploads/2017/11/Screen-Shot-2017-11-04-at-10.50.31.png)

No distractions like on the other sites. Just enter a domain to get some records. It couldn't be simpler.

 ![](https://freek.dev/uploads/2017/11/Screen-Shot-2017-11-04-at-10.52.17.png)

The results are displayed on a link which has a sharable link. You can just visit [https://dnsrecords.io/facebook.com](https://dnsrecords.io/facebook.com) to get the dns records of Facebook.

If you type `help` you see some extra commands you can execute on our app:

 ![](https://freek.dev/uploads/2017/11/Screen-Shot-2017-11-04-at-10.57.51.png)

Our real killer feature is of course that you can play [Doom](https://js-dos.com/games/doom.exe.html). Go on and waste some hours with this excellent game. When you're done with that go on and drag that bookmarklet to your toolbar to lookup the dns records of the sites you visit.

### Behind the scenes

We've open sourced the entire site. You can view the code that's actually being deployed to our server in this repo [on GitHub](https://github.com/spatie/dnsrecords.io).

Let's walk a bit through the code. When looking at an early version of the only controller in this project, you'll see that [everything happened](https://github.com/spatie/dnsrecords.io/blob/40de729e67ed38033f9636f6cf5f7843cd898166/app/Http/Controllers/HomeController.php) inside that single controller. But because we want to easily add more commands in the features we refactored it quite a bit. In the [current version the controller](https://raw.githubusercontent.com/spatie/dnsrecords.io/master/app/Http/Controllers/HomeController.php) is quite skinny:

```
<?php

namespace App\Http\Controllers;

use App\Services\Commands\CommandChain;
use Illuminate\Http\Request;

class HomeController extends Controller
{
    public function index()
    {
        return view('home.index');
    }

    public function submit($command = null, Request $request)
    {
        $command = $request['command'] ?? $command;

        if (!$command) {
            return $this->index();
        }

        return (new CommandChain())->perform(strtolower($command));
    }
}
```

Every submitted `$command` is delegated to a `CommandChain`. Let's take a look at the code of that `CommandChain`.

```
<br />class CommandChain
{
    protected $commands = [
        Manual::class,
        Localhost::class,
        Clear::class,
        Ip::class,
        Doom::class,
        DnsLookup::class,
    ];

    public function perform(string $command): Response
    {
        return collect($this->commands)
                ->map(function (string $commandClassName) {
                    return new $commandClassName;
                })
                ->first->canPerform($command)
                ->perform($command);
    }
}
```

You'll see above that we register some command classes to the chain. In `perform` we'll instanciate them. The chain will ask each class: "can you perform this `$command`? ". The first one that can will actually perform that `$command`.

Let's take a look at a such a command class. Here's the code of [the `Doom` command](https://github.com/spatie/dnsrecords.io/blob/master/app/Services/Commands/Commands/Doom.php):

```
namespace App\Services\Commands\Commands;

use App\Services\Commands\Command;
use Symfony\Component\HttpFoundation\Response;

class Doom implements Command
{
    public function canPerform(string $command): bool
    {
        return $command === 'doom';
    }

    public function perform(string $command): Response
    {
        return redirect('https://js-dos.com/games/doom.exe.html');
    }
}
```

The `perform` function of a `Command` class always returns are `Illuminate\Http\Response`. In case of the `Doom` command we'll just return a redirect to a site where you can play Doom.

Let's take a look at another command, [the `DnsLookup` command](https://github.com/spatie/dnsrecords.io/blob/master/app/Services/Commands/Commands/DnsLookup.php):

```
namespace App\Services\Commands\Commands;

use App\Services\Commands\Command;
use App\Services\DnsRecordsRetriever;
use Spatie\Dns\Dns;
use Symfony\Component\HttpFoundation\Response;

class DnsLookup implements Command
{
    public function canPerform(string $command): bool
    {
        return true;
    }

    public function perform(string $command): Response
    {
        $dns = new Dns($command);

        $dnsRecords = $dns->getRecords();

        $domain = $dns->getDomain($command);

        if ($dnsRecords === '') {
            $errorText = __('errors.noDnsRecordsFound', compact('domain'));

            flash()->error($errorText);

            return redirect('/');
        }

        return response()->view('home.index', ['output' => $dnsRecords, 'domain' => $domain]);
    }
}
```

Noticed that `canPerform` returns `true`. This command basically says, I can handle everything. If you look again `$commands` array in the `CommandChain` you'll see that `DnsLookup` is registered last. So when no other Command can handle the `$command` the `DnsLookup` will do its thing.

The real magic of looking up dns records happens inside that `Spatie\Dns\Dns` object which is part of [our spatie/dns package](https://github.com/spatie/dns).

Here's how you can use it:

```
$dns = new Spatie\Dns('spatie.be');

$dns->getRecords(); // returns all records

$dns->getRecords('A'); // returns only A records
$dns->getRecords('MX'); // returns only MX records

$dns->getRecords('A', 'MX'); // returns both A and MX records
$dns->getRecords(['A', 'MX']); // returns both A and MX records
```

The actual lookup of dns records inside that package is being done by calling [`dig`](http://www.thegeekstuff.com/2012/02/dig-command-examples/), a command line tool to lookup dns related info.

Here is the relevant function inside [the `Spatie\Dns\Dns` class](https://github.com/spatie/dns/blob/master/src/Dns.php) where that call happens.

```
protected function getRecordsOfType(string $type): string
{
    $command = 'dig +nocmd '.escapeshellarg($this->domain)." {$type} +multiline +noall +answer";

    $process = new Process($command);

    $process->run();

    if (! $process->isSuccessful()) {
        throw new Exception('Dns records could not be fetched');
    }

    return $process->getOutput();
}
```

### In closing

I hope you've enjoyed this little behind the scenes of [https://dnsrecords.io](https://dnsrecords.io). I'd like to emphasise that creating this service was a [team](https://spatie.be/en/team) effort. Every member of our team helped with making the code better. We also got some great contributions from the community for which we are grateful.

This is not the first project that we've open sourced. If you like to see some more work by our team, take a look at our [Dashboard](https://freek.dev/2017/06/building-realtime-dashboard-powered-laravel-vue-2017-edition/), or the many [Laravel](https://spatie.be/en/opensource/laravel), [PHP](https://spatie.be/en/opensource/php) and [JavaScript](https://spatie.be/en/opensource/javascript) packages we created previously. Want to support our open source efforts? Then consider, becoming [a patreon](https://www.patreon.com/spatie).