Scout APM is PHP application performance monitoring designed for developers. With tracing logic that ties issues back to the line of code causing them, you can pinpoint n+1 queries, memory leaks, and other abnormalities in real time so you can knock them out and get back to building a great product. Start your free 14-day trial today and get the performance insight you need in less than 4 minutes.

A beautiful webapp to fetch dns records

Original – by Freek Van der Herten – 3 minute read

Recently my company Spatie launched, a beautiful site to quickly lookup dns records.

True to form, we also opensourced it, here is the sourcecode on GitHub. 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.

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, our project manager at Spatie, 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 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] ( Here are a few of the first hits:

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.


My colleague Willem did an excellent job in making look beautiful. This is what you see when visiting the site.

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

The results are displayed on a link which has a sharable link. You can just visit to get the dns records of Facebook.

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

Our real killer feature is of course that you can play Doom. 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.

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 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 is quite skinny:


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 = [

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

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:

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('');

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:

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'));


            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.

Here's how you can use it:

$dns = new Spatie\Dns('');

$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, a command line tool to lookup dns related info.

Here is the relevant function inside the Spatie\Dns\Dns class where that call happens.

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

    $process = new Process($command);


    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 I'd like to emphasise that creating this service was a 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, or the many Laravel, PHP and JavaScript packages we created previously. Want to support our open source efforts? Then consider, becoming a patreon.

Stay up to date with all things Laravel, PHP, and JavaScript.

Follow me on Twitter. I regularly tweet out programming tips, and what I myself have learned in ongoing projects.

Every two weeks I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.