This article is written by André Castelo and originally posted at Toptal

With the rise of mobile development and JavaScript frameworks, using a RESTful API is the best option to build a single interface between your data and your client.

Laravel is a PHP framework developed with PHP developer productivity in mind. Written and maintained by Taylor Otwell, the framework is very opinionated and strives to save developer time by favoring convention over configuration. The framework also aims to evolve with the web and has already incorporated several new features and ideas in the web development world—such as job queues, API authentication out of the box, real-time communication, and much more.

Laravel API Tutorial - Building a RESTful Web service

In this tutorial, we’ll explore the ways you can build—and test—a robust API using Laravel with authentication. We’ll be using Laravel 5.4, and all of the code is available for reference on GitHub.

RESTful APIs

First, we need to understand what exactly is considered a RESTful API. REST stands for REpresentational State Transfer and is an architectural style for network communication between applications, which relies on a stateless protocol (usually HTTP) for interaction.

HTTP Verbs Represent Actions

In RESTful APIs, we use the HTTP verbs as actions, and the endpoints are the resources acted upon. We’ll be using the HTTP verbs for their semantic meaning:

  • GET: retrieve resources
  • POST: create resources
  • PUT: update resources
  • DELETE: delete resources
HTTP verbs: GET, POST, PUT and DELETE are actions in RESTful APIs

Update Action: PUT vs. POST

RESTful APIs are a matter of much debate and there are plenty of opinions out there on whether is best to update with POSTPATCH, or PUT, or if the create action is best left to the PUT verb. In this article we’ll be using PUT for the update action, as according to the HTTP RFC, PUT means to create/update a resource at a specific location. Another requirement for the PUT verb is idempotence, which in this case basically means you can send that request 1, 2 or 1000 times and the result will be the same: one updated resource in the database.

Resources

Resources will be the targets of the actions, in our case Articles and Users, and they have their own endpoints:

  • /articles
  • /users

In this laravel api tutorial, the resources will have a 1:1 representation on our data models, but that is not a requirement. You can have resources represented in more than one data model (or not represented at all in the database) and models completely off limits for the user. In the end, you get to decide how to architect resources and models in a way that is fitting to your application.

A Note on Consistency

The greatest advantage of using a set of conventions such as REST is that your API will be much easier to consume and develop around. Some endpoints are pretty straightforward and, as a result, your API will be much more easier to use and maintain as opposed to having endpoints such as GET /get_article?id_article=12 and POST /delete_article?number=40. I’ve built terrible APIs like that in the past and I still hate myself for it.

However, there will be cases where it will be hard to map to a Create/Retrieve/Update/Delete schema. Remember that the URLs should not contain verbs and that resources are not necessarily rows in a table. Another thing to keep in mind is that you don’t have to implement every action for every resource.

Setting Up a Laravel Web Service Project

As with all modern PHP frameworks, we’ll need Composer to install and handle our dependencies. After you follow the download instructions (and add to your path environment variable), install Laravel using the command:

$ composer global require laravel/installer

After the installation finishes, you can scaffold a new application like this:

$ laravel new myapp

For the above command, you need to have ~/composer/vendor/bin in your $PATH. If you don’t want to deal with that, you can also create a new project using Composer:

$ composer create-project --prefer-dist laravel/laravel myapp

With Laravel installed, you should be able to start the server and test if everything is working:

$ php artisan serve
Laravel development server started: <http://127.0.0.1:8000>
When you open localhost:8000 on your browser, you should see the Laravel sample page
When you open localhost:8000 on your browser, you should see this sample page.

Migrations and Models

Before actually writing your first migration, make sure you have a database created for this app and add its credentials to the .env file located in the root of the project.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

You can also use Homestead, a Vagrant box specially crafted for Laravel, but that is a bit out of the scope of this article. If you’d like to know more, refer to the Homestead documentation.

Let’s get started with our first model and migration—the Article. The article should have a title and a body field, as well as a creation date. Laravel provides several commands through Artisan—Laravel’s command line tool—that help us by generating files and putting them in the correct folders. To create the Article model, we can run:

$ php artisan make:model Article -m

The -m option is short for --migration and it tells Artisan to create one for our model. Here’s the generated migration:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateArticlesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('articles', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('articles');
    }
}

Let’s dissect this for a second:

  • The up() and down() methods will be run when we migrate and rollback respectively;
  • $table->increments('id') sets up an auto incrementing integer with the name id;
  • $table->timestamps() will set up the timestamps for us—created_at and updated_at, but don’t worry about setting a default, Laravel takes care of updating these fields when needed.
  • And finally, Schema::dropIfExists() will, of course, drop the table if it exists.

With that out of the way, let’s add two lines to our up() method:

public function up()
{
    Schema::create('articles', function (Blueprint $table) {
        $table->increments('id');
        $table->string('title');
        $table->text('body');
        $table->timestamps();
    });
}

The string() method creates a VARCHAR equivalent column while text() creates a TEXTequivalent. With that done, let’s go ahead and migrate:

$ php artisan migrate

You can also use the --step option here, and it will separate each migration into its own batch so that you can roll them back individually if needed.

Laravel out of the box comes with two migrations, create_users_table and create_password_resets_table. We won’t be using the password_resets table, but having the users table ready for us will be helpful.

Now let’s go back to our model and add those attributes to the $fillable field so that we can use them in our Article::create and Article::update models:

class Article extends Model
{
    protected $fillable = ['title', 'body'];
}

Fields inside the $fillable property can be mass assigned using Eloquent’s create() and update()methods. You can also use the $guarded property, to allow all but a few properties.

Database Seeding

Database seeding is the process of filling up our database with dummy data that we can use to test it. Laravel comes with Faker, a great library for generating just the correct format of dummy data for us. So let’s create our first seeder:

$ php artisan make:seeder ArticlesTableSeeder

The seeders will be located in the /database/seeds directory. Here’s how it looks like after we set it up to create a few articles:

class ArticlesTableSeeder extends Seeder
{
    public function run()
    {
        // Let's truncate our existing records to start from scratch.
        Article::truncate();

        $faker = \Faker\Factory::create();

        // And now, let's create a few articles in our database:
        for ($i = 0; $i < 50; $i++) {
            Article::create([
                'title' => $faker->sentence,
                'body' => $faker->paragraph,
            ]);
        }
    }
}

So let’s run the seed command:

$ php artisan db:seed --class=ArticlesTableSeeder

Let’s repeat the process to create a Users seeder:

class UsersTableSeeder extends Seeder
{
    public function run()
    {
        // Let's clear the users table first
        User::truncate();

        $faker = \Faker\Factory::create();

        // Let's make sure everyone has the same password and 
        // let's hash it before the loop, or else our seeder 
        // will be too slow.
        $password = Hash::make('toptal');

        User::create([
            'name' => 'Administrator',
            'email' => 'admin@test.com',
            'password' => $password,
        ]);

        // And now let's generate a few dozen users for our app:
        for ($i = 0; $i < 10; $i++) {
            User::create([
                'name' => $faker->name,
                'email' => $faker->email,
                'password' => $password,
            ]);
        }
    }
}

We can make it easier by adding our seeders to the main DatabaseSeeder class inside the database/seedsfolder:

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call(ArticlesTableSeeder::class);
        $this->call(UsersTableSeeder::class);
    }
}

This way, we can simply run $ php artisan db:seed and it will run all the called classes in the run() method.

Routes and Controllers

Let’s create the basic endpoints for our application: create, retrieve the list, retrieve a single one, update, and delete. On the routes/api.php file, we can simply do this:

Use App\Article;
 
Route::get('articles', function() {
    // If the Content-Type and Accept headers are set to 'application/json', 
    // this will return a JSON structure. This will be cleaned up later.
    return Article::all();
});
 
Route::get('articles/{id}', function($id) {
    return Article::find($id);
});

Route::post('articles', function(Request $request) {
    return Article::create($request->all);
});

Route::put('articles/{id}', function(Request $request, $id) {
    $article = Article::findOrFail($id);
    $article->update($request->all());

    return $article;
});

Route::delete('articles/{id}', function($id) {
    Article::find($id)->delete();

    return 204;
})

The routes inside api.php will be prefixed with /api/ and the API throttling middleware will be automatically applied to these routes (if you want to remove the prefix you can edit the RouteServiceProvider class on /app/Providers/RouteServiceProvider.php).

Now let’s move this code to its own Controller:

$ php artisan make:controller ArticleController

ArticleController.php:

use App\Article;
 
class ArticleController extends Controller
{
    public function index()
    {
        return Article::all();
    }
 
    public function show($id)
    {
        return Article::find($id);
    }

    public function store(Request $request)
    {
        return Article::create($request->all());
    }

    public function update(Request $request, $id)
    {
        $article = Article::findOrFail($id);
        $article->update($request->all());

        return $article;
    }

    public function delete(Request $request, $id)
    {
        $article = Article::findOrFail($id);
        $article->delete();

        return 204;
    }
}

The routes/api.php file:

Route::get('articles', 'ArticleController@index');
Route::get('articles/{id}', 'ArticleController@show');
Route::post('articles', 'ArticleController@store');
Route::put('articles/{id}', 'ArticleController@update');
Route::delete('articles/{id}', 'ArticleController@delete');

We can improve the endpoints by using implicit route model binding. This way, Laravel will inject the Articleinstance in our methods and automatically return a 404 if it isn’t found. We’ll have to make changes on the routes file and on the controller:

Route::get('articles', 'ArticleController@index');
Route::get('articles/{article}', 'ArticleController@show');
Route::post('articles', 'ArticleController@store');
Route::put('articles/{article}', 'ArticleController@update');
Route::delete('articles/{article}', 'ArticleController@delete');
class ArticleController extends Controller
{
    public function index()
    {
        return Article::all();
    }

    public function show(Article $article)
    {
        return $article;
    }

    public function store(Request $request)
    {
        $article = Article::create($request->all());

        return response()->json($article, 201);
    }

    public function update(Request $request, Article $article)
    {
        $article->update($request->all());

        return response()->json($article, 200);
    }

    public function delete(Article $article)
    {
        $article->delete();

        return response()->json(null, 204);
    }
}

A Note on HTTP Status Codes and the Response Format

We’ve also added the response()->json() call to our endpoints. This lets us explicitly return JSON data as well as send an HTTP code that can be parsed by the client. The most common codes you’ll be returning will be:

  • 200: OK. The standard success code and default option.
  • 201: Object created. Useful for the store actions.
  • 204: No content. When an action was executed successfully, but there is no content to return.
  • 206: Partial content. Useful when you have to return a paginated list of resources.
  • 400: Bad request. The standard option for requests that fail to pass validation.
  • 401: Unauthorized. The user needs to be authenticated.
  • 403: Forbidden. The user is authenticated, but does not have the permissions to perform an action.
  • 404: Not found. This will be returned automatically by Laravel when the resource is not found.
  • 500: Internal server error. Ideally you’re not going to be explicitly returning this, but if something unexpected breaks, this is what your user is going to receive.
  • 503: Service unavailable. Pretty self explanatory, but also another code that is not going to be returned explicitly by the application.

Sending a Correct 404 Response

If you tried to fetch a non-existent resource, you’ll be thrown an exception and you’ll receive the whole stacktrace, like this:

NotFoundHttpException Stacktrace

We can fix that by editing our exception handler class, located in app/Exceptions/Handler.php, to return a JSON response:

public function render($request, Exception $exception)
{
    // This will replace our 404 response with
    // a JSON response.
    if ($exception instanceof ModelNotFoundException) {
        return response()->json([
            'error' => 'Resource not found'
        ], 404);
    }

    return parent::render($request, $exception);
}

Here’s an example of the return:

{
    data: "Resource not found"
}

If you’re using Laravel to serve other pages, you have to edit the code to work with the Accept header, otherwise 404 errors from regular requests will return a JSON as well.

public function render($request, Exception $exception)
{
    // This will replace our 404 response with
    // a JSON response.
    if ($exception instanceof ModelNotFoundException &&
        $request->wantsJson())
    {
        return response()->json([
            'data' => 'Resource not found'
        ], 404);
    }

    return parent::render($request, $exception);
}

In this case, the API requests will need the header Accept: application/json.

Authentication

There are many ways to implement API Authentication in Laravel (one of them being Passport, a great way to implement OAuth2), but in this article, we’ll take a very simplified approach.

To get started, we’ll need to add an api_token field to the users table:

$ php artisan make:migration --table=users adds_api_token_to_users_table

And then implement the migration:

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('api_token', 60)->unique()->nullable();
    });
}

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn(['api_token']);
    });
}

After that, just run the migration using:

$ php artisan migrate

Creating the Register Endpoint

We’ll make use of the RegisterController (in the Auth folder) to return the correct response upon registration. Laravel comes with authentication out of the box, but we still need to tweak it a bit to return the response we want.

If APIs were in English, this is what an api authentication conversation would sound like

The controller makes use of the trait RegistersUsers to implement the registration. Here’s how it works:

public function register(Request $request)
{
    // Here the request is validated. The validator method is located
    // inside the RegisterController, and makes sure the name, email
    // password and password_confirmation fields are required.
    $this->validator($request->all())->validate();

    // A Registered event is created and will trigger any relevant
    // observers, such as sending a confirmation email or any 
    // code that needs to be run as soon as the user is created.
    event(new Registered($user = $this->create($request->all())));

    // After the user is created, he's logged in.
    $this->guard()->login($user);

    // And finally this is the hook that we want. If there is no
    // registered() method or it returns null, redirect him to
    // some other URL. In our case, we just need to implement
    // that method to return the correct response.
    return $this->registered($request, $user)
                    ?: redirect($this->redirectPath());
}

We just need to implement the registered() method in our RegisterController. The method receives the $request and the $user, so that’s really all we want. Here’s how the method should look like inside the controller:

protected function registered(Request $request, $user)
{
    $user->generateToken();

    return response()->json(['data' => $user->toArray()], 201);
}

And we can link it on the routes file:

Route::post(register, 'Auth\RegisterController@register);

In the section above, we used a method on the User model to generate the token. This is useful so that we only have a single way of generating the tokens. Add the following method to your User model:

class User extends Authenticatable
{
    ...
    public function generateToken()
    {
        $this->api_token = str_random(60);
        $this->save();

        return $this->api_token;
    }
}

And that’s it. The user is now registered and thanks to Laravel’s validation and out of the box authentication, the nameemailpassword, and password_confirmation fields are required, and the feedback is handled automatically. Checkout the validator() method inside the RegisterController to see how the rules are implemented.

Here’s what we get when we hit that endpoint:

$ curl -X POST http://localhost:8000/api/register \
 -H "Accept: application/json" \
 -H "Content-Type: application/json" \
 -d '{"name": "John", "email": "john.doe@toptal.com", "password": "toptal123", "password_confirmation": "toptal123"}'
{
    "data": {
        "api_token":"0syHnl0Y9jOIfszq11EC2CBQwCfObmvscrZYo5o2ilZPnohvndH797nDNyAT",
        "created_at": "2017-06-20 21:17:15",
        "email": "john.doe@toptal.com",
        "id": 51,
        "name": "John",
        "updated_at": "2017-06-20 21:17:15"
    }
}

Creating a Login Endpoint

Just like the registration endpoint, we can edit the LoginController (in the Auth folder) to support our API authentication. The login method of the AuthenticatesUsers trait can be overridden to support our API:

public function login(Request $request)
{
    $this->validateLogin($request);

    if ($this->attemptLogin($request)) {
        $user = $this->guard()->user();
        $user->generateToken();

        return response()->json([
            'data' => $user->toArray(),
        ]);
    }

    return $this->sendFailedLoginResponse($request);
}

And we can link it on the routes file:

Route::post('login', 'Auth\LoginController@login');

Now, assuming the seeders have been run, here’s what we get when we send a POST request to that route:

$ curl -X POST localhost:8000/api/login \
  -H "Accept: application/json" \
  -H "Content-type: application/json" \
  -d "{\"email\": \"admin@test.com\", \"password\": \"toptal\" }"
{
    "data": {
        "id":1,
        "name":"Administrator",
        "email":"admin@test.com",
        "created_at":"2017-04-25 01:05:34",
        "updated_at":"2017-04-25 02:50:40",
        "api_token":"Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw"
    }
}

To send the token in a request, you can do it by sending an attribute api_token in the payload or as a bearer token in the request headers in the form of Authorization: Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw.

Logging Out

With our current strategy, if the token is wrong or missing, the user should receive an unauthenticated response (which we’ll implement in the next section). So for a simple logout endpoint, we’ll send in the token and it will be removed on the database.

routes/api.php:

Route::post('logout', 'Auth\LoginController@logout');

Auth\LoginController.php:

public function logout(Request $request)
{
    $user = Auth::guard('api')->user();

    if ($user) {
        $user->api_token = null;
        $user->save();
    }

    return response()->json(['data' => 'User logged out.'], 200);
}

Using this strategy, whatever token the user has will be invalid, and the API will deny access (using middlewares, as explained in the next section). This needs to be coordinated with the front-end to avoid the user remaining logged without having access to any content.

Using Middlewares to Restrict Access

With the api_token created, we can toggle the authentication middleware in the routes file:

Route::middleware('auth:api')
    ->get('/user', function (Request $request) {
        return $request->user();
    });

We can access the current user using the $request->user() method or through the Auth facade

Auth::guard('api')->user(); // instance of the logged user
Auth::guard('api')->check(); // if a user is authenticated
Auth::guard('api')->id(); // the id of the authenticated user

And we get a result like this:

An InvalidArgumentException Stacktrace

This is because we need to edit the current unauthenticated method on our Handler class. The current version returns a JSON only if the request has the Accept: application/json header, so let’s change it:

protected function unauthenticated($request, AuthenticationException $exception)
{
    return response()->json(['error' => 'Unauthenticated'], 401);
}

With that fixed, we can go back to the article endpoints to wrap them in the auth:api middleware. We can do that by using route groups:

Route::group(['middleware' => 'auth:api'], function() {
    Route::get('articles', 'ArticleController@index');
    Route::get('articles/{article}', 'ArticleController@show');
    Route::post('articles', 'ArticleController@store');
    Route::put('articles/{article}', 'ArticleController@update');
    Route::delete('articles/{article}', 'ArticleController@delete');
});

This way we don’t have to set the middleware for each of the routes. It doesn’t save a lot of time right now, but as the project grows it helps to keep the routes DRY.

Testing Our Endpoints

Laravel includes integration with PHPUnit out of the box with a phpunit.xml already set up. The framework also provides us with several helpers and extra assertions that makes our lives much easier, especially for testing APIs.

There are a number of external tools you can use to test your API; however, testing inside Laravel is a much better alternative—we can have all the benefits of testing an API structure and results while retaining full control of the database. For the list endpoint, for example, we could run a couple of factories and assert the response contains those resources.

To get started, we’ll need to tweak a few settings to use an in-memory SQLite database. Using that will make our tests run lightning fast, but the trade-off is that some migration commands (constraints, for example) will not work properly in that particular setup. I advise moving away from SQLite in testing when you start getting migration errors or if you prefer a stronger set of tests instead of performant runs.

We’ll also run the migrations before each test. This setup will allow us to build the database for each test and then destroy it, avoiding any type of dependency between tests.

In our config/database.php file, we’ll need to set up the database field in the sqlite configuration to :memory::

...
'connections' => [

    'sqlite' => [
        'driver' => 'sqlite',
        'database' => ':memory:',
        'prefix' => '',
    ],
    
    ...
]

Then enable SQLite in phpunit.xml by adding the environment variable DB_CONNECTION:

    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
        <env name="DB_CONNECTION" value="sqlite"/>
    </php>

With that out of the way, all that’s left is configuring our base TestCase class to use migrations and seed the database before each test. To do so, we need to add the DatabaseMigrations trait, and then add an Artisancall on our setUp() method. Here’s the class after the changes:

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Support\Facades\Artisan;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication, DatabaseMigrations;

    public function setUp()
    {
        parent::setUp();
        Artisan::call('db:seed');
    }
}

One last thing that I like to do is to add the test command to composer.json:

    "scripts": {
        "test" : [
            "vendor/bin/phpunit"
        ],
    ... 
    },    

The test command will be available like this:

$ composer test

Setting Up Factories for Our Tests

Factories will allow us to quickly create objects with the right data for testing. They’re located in the database/factories folder. Laravel comes out of the box with a factory for the User class, so let’s add one for the Article class:

$factory->define(App\Article::class, function (Faker\Generator $faker) {
    return [
        'title' => $faker->sentence,
        'body' => $faker->paragraph,
    ];
});

The Faker library is already injected to help us create the correct format of random data for our models.

Our First Tests

We can use Laravel’s assert methods to easily hit an endpoint and evaluate its response. Let’s create our first test, the login test, using the following command:

$ php artisan make:test Feature/LoginTest

And here is our test:

class LoginTest extends TestCase
{
    public function testRequiresEmailAndLogin()
    {
        $this->json('POST', 'api/login')
            ->assertStatus(422)
            ->assertJson([
                'email' => ['The email field is required.'],
                'password' => ['The password field is required.'],
            ]);
    }


    public function testUserLoginsSuccessfully()
    {
        $user = factory(User::class)->create([
            'email' => 'testlogin@user.com',
            'password' => bcrypt('toptal123'),
        ]);

        $payload = ['email' => 'testlogin@user.com', 'password' => 'toptal123'];

        $this->json('POST', 'api/login', $payload)
            ->assertStatus(200)
            ->assertJsonStructure([
                'data' => [
                    'id',
                    'name',
                    'email',
                    'created_at',
                    'updated_at',
                    'api_token',
                ],
            ]);

    }
}

These methods test a couple of simple cases. The json() method hits the endpoint and the other asserts are pretty self explanatory. One detail about assertJson(): this method converts the response into an array searches for the argument, so the order is important. You can chain multiple assertJson() calls in that case.

Now, let’s create the register endpoint test and write a couple for that endpoint:

$ php artisan make:test RegisterTest
class RegisterTest extends TestCase
{
    public function testsRegistersSuccessfully()
    {
        $payload = [
            'name' => 'John',
            'email' => 'john@toptal.com',
            'password' => 'toptal123',
            'password_confirmation' => 'toptal123',
        ];

        $this->json('post', '/api/register', $payload)
            ->assertStatus(201)
            ->assertJsonStructure([
                'data' => [
                    'id',
                    'name',
                    'email',
                    'created_at',
                    'updated_at',
                    'api_token',
                ],
            ]);;
    }

    public function testsRequiresPasswordEmailAndName()
    {
        $this->json('post', '/api/register')
            ->assertStatus(422)
            ->assertJson([
                'name' => ['The name field is required.'],
                'email' => ['The email field is required.'],
                'password' => ['The password field is required.'],
            ]);
    }

    public function testsRequirePasswordConfirmation()
    {
        $payload = [
            'name' => 'John',
            'email' => 'john@toptal.com',
            'password' => 'toptal123',
        ];

        $this->json('post', '/api/register', $payload)
            ->assertStatus(422)
            ->assertJson([
                'password' => ['The password confirmation does not match.'],
            ]);
    }
}

And lastly, the logout endpoint:

$ php artisan make:test LogoutTest
class LogoutTest extends TestCase
{
    public function testUserIsLoggedOutProperly()
    {
        $user = factory(User::class)->create(['email' => 'user@test.com']);
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];

        $this->json('get', '/api/articles', [], $headers)->assertStatus(200);
        $this->json('post', '/api/logout', [], $headers)->assertStatus(200);

        $user = User::find($user->id);

        $this->assertEquals(null, $user->api_token);
    }

    public function testUserWithNullToken()
    {
        // Simulating login
        $user = factory(User::class)->create(['email' => 'user@test.com']);
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];

        // Simulating logout
        $user->api_token = null;
        $user->save();

        $this->json('get', '/api/articles', [], $headers)->assertStatus(401);
    }
}

It’s important to note that, during testing, the Laravel application is not instantiated again on a new request. Which means that when we hit the authentication middleware, it saves the current user inside the TokenGuard instance to avoid hitting the database again. A wise choice, however—in this case, it means we have to split the logout test into two, to avoid any issues with the previously cached user.

Testing the Article endpoints is straightforward as well:

class ArticleTest extends TestCase
{
    public function testsArticlesAreCreatedCorrectly()
    {
        $user = factory(User::class)->create();
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];
        $payload = [
            'title' => 'Lorem',
            'body' => 'Ipsum',
        ];

        $this->json('POST', '/api/articles', $payload, $headers)
            ->assertStatus(200)
            ->assertJson(['id' => 1, 'title' => 'Lorem', 'body' => 'Ipsum']);
    }

    public function testsArticlesAreUpdatedCorrectly()
    {
        $user = factory(User::class)->create();
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];
        $article = factory(Article::class)->create([
            'title' => 'First Article',
            'body' => 'First Body',
        ]);

        $payload = [
            'title' => 'Lorem',
            'body' => 'Ipsum',
        ];

        $response = $this->json('PUT', '/api/articles/' . $article->id, $payload, $headers)
            ->assertStatus(200)
            ->assertJson([ 
                'id' => 1, 
                'title' => 'Lorem', 
                'body' => 'Ipsum' 
            ]);
    }

    public function testsArtilcesAreDeletedCorrectly()
    {
        $user = factory(User::class)->create();
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];
        $article = factory(Article::class)->create([
            'title' => 'First Article',
            'body' => 'First Body',
        ]);

        $this->json('DELETE', '/api/articles/' . $article->id, [], $headers)
            ->assertStatus(204);
    }

    public function testArticlesAreListedCorrectly()
    {
        factory(Article::class)->create([
            'title' => 'First Article',
            'body' => 'First Body'
        ]);

        factory(Article::class)->create([
            'title' => 'Second Article',
            'body' => 'Second Body'
        ]);

        $user = factory(User::class)->create();
        $token = $user->generateToken();
        $headers = ['Authorization' => "Bearer $token"];

        $response = $this->json('GET', '/api/articles', [], $headers)
            ->assertStatus(200)
            ->assertJson([
                [ 'title' => 'First Article', 'body' => 'First Body' ],
                [ 'title' => 'Second Article', 'body' => 'Second Body' ]
            ])
            ->assertJsonStructure([
                '*' => ['id', 'body', 'title', 'created_at', 'updated_at'],
            ]);
    }

}

Next Steps

That’s all there is to it. There’s definitely room for improvement—you can implement OAuth2 with the Passportpackage, integrate a pagination and transformation layer (I recommend Fractal), the list goes on—but I wanted to go through the basics of creating and testing an API in Laravel with no external packages.

Laravel has certainly improved my experience with PHP and the ease of testing with it has solidified my interest in the framework. It’s not perfect, but it’s flexible enough to let you work around its issues.

If you’re designing a public API, check out 5 Golden Rules for Great Web API Design.

This article is written by André Castelo and originally posted at Toptal

About the Author: André Castelo is a web developer focused on PHP and JavaScript with experience developing apps with Laravel and CodeIgniter as well as APIs in Laravel and client-side single-page apps using AngularJS. He's equally comfortable working with a team or flying solo.

Here are the top 15 places to find a WordPress developer:

1. Toptal

Toptal is a matching service, initially created with only tech talent in mind. Although it has expanded its pool of talent to include designers and finance experts, the company’s bread and butter is its developer vertical. If you want to be sure that a WordPress developer is up to the job, hiring an exceptional developer from Toptal is likely your best option.

Why? Toptal boasts an elite developer base. Their trademark system for vetting talent allows for only the best to become a part of their community. According to Toptal, only 3% of applicants make it through their battery of technical tests and their comprehensive vetting process.

2. Codeable.io

Codeable.io is a curated marketplace of WordPress developers. Unlike most other freelance marketplaces, Codeable focuses on a niche and only retains WordPress developers. According to Codeable, over 64,000 jobs have been completed by their developers with a satisfaction rate of nearly 99%.

Codeable’s standards for its pool of developers is a cut above most freelance marketplaces. The site boasts that its 300+ WordPress freelancers comprise the top 2% of applicants.

3. Hired

Hired helps employers find software engineers and developers. On Hired, you can use their pipeline to find custom matches. You can create a company profile, search for candidates using their search algorithm (which can eliminate gender and racial identifiers for fairer hiring), and request interviews with candidates.

What’s best about Hired? It’s great for finding specialized WordPress developers who are actively searching for new opportunities, have relevant experience (as most candidates on Hired have at least two years of experience), and are in your area.

4. GitHub Jobs

Don’t waste your time perusing large job boards like Monster and Indeed. You’ll have far better luck with job boards geared toward tech talent. GitHub has a massive developer community as it’s one of the largest open-source online repositories for coders. For a relatively small fee, you can post a WordPress developer job listing and gain a great deal of exposure on GitHub’s huge developer community.

5. Stack Overflow

Stack Overflow has an online community that rivals GitHub. Arguably, it’s the absolute largest and most trusted community of developers on the web. Stack Overflow is often used as a resource for all kinds of developers, novice to expert, seeking to learn more about coding. Their job board, like GitHub’s, allows for an incredible amount of exposure to dedicated WordPress developers around the world.

6. Upwork

If recruiting services and job boards aren’t your thing, you might want to consider a freelance marketplace like Upwork.

Upwork has one of the largest marketplaces with millions of registered freelancers. You can hire contractors for a few simple coding tasks or begin a long-term relationship with a series of complex WordPress projects. If you like the idea of finding, interviewing, and managing freelancers, Upwork’s colossal marketplace will likely meet your needs.

7. People Per Hour

People Per Hour is another freelance marketplace akin to Upwork. What makes People Per Hour unique is that it holds contests and allows Freelancers to post their own job postings called hourlies.

People Per Hour has millions of members, thousands of confirmed hours, and a plethora of success stories from freelancers and entrepreneurs alike. The ease of posting jobs, contacting freelancers, and paying for hours worked makes People Per Hour a superb choice for employers interested in searching for and vetting freelance candidates themselves.

Additionally, with People Per Hour, you can connect with local freelancers, so you aren’t necessarily limited to remote talent.

8. WP Hired

WP Hired is a fairly large job board with thousands of job listings for WordPress-related job openings. WordPress jobs are organized by category. You can post job listings under WordPress migration, performance, plugin development, theme development, and programming.

You can post one job listing for free on WP Hired. Monthly plans that expand upon the number of listings and available features are available as well.

9. Gun.io

Gun.io has a growing community of developers over 25,000 strong. Like Toptal, their service is designed to take the tedium out of hiring. Gun.io vets their talent and ensures that their freelancers are committed to each and every project.

Gun.io prides itself on its humanism. Every employer is connected with a VP, instead of a sales representative, and freelancers are provided with the resources to succeed.

What’s most alluring about the network? Gun.io manages and replaces talent – with no risk to you – and back every single hour worked with a 100% money-back guarantee.

10. Guru

Guru has a large global network of freelancers—albeit smaller than Upwork’s and People Per Hour’s massive pool. You can explore the profiles of 1.5 million gurus, propose projects, and pay your hired talent with their secure SafePay system.

Guru isn’t focused on developers, let alone Angular developers, as it is a freelance network comprised of every sort of professional. So, like with Upwork and People Per Hour, you’ll have to narrow your search yourself. Also, as with many freelance networks, the vetting and interviewing will be up to you.

11. Freelancer

Freelancer is an absolutely massive marketplace with 25 million registered users, 12 million total posted jobs, and thousands of completed projects. With the size of the marketplace comes a unique challenge, however. Finding the perfect WordPress developer is like finding a needle in a haystack.

Although website development is one of the most popular job categories on Freelancer, you still will have to search through thousands of Freelancer profiles, vet and interview candidates yourself, and manage payments yourself.

If you’re looking for an affordable option, however, Freelancer is a wonderful hiring solution. For more long-term commitments, you’ll want to consider matching services like Toptal, Codeable.io, or Gun.io.

12. WordPress Jobs

WordPress Jobs publishes only WordPress-related job listings. So, while GitHub and StackOverflow’s job boards give you access to giant developer communities, WordPress-specific job boards help you pinpoint only WordPress developers.

In addition, posting on WordPress Jobs is free, making it an affordable option for small businesses.

13. Find Bacon

Find Bacon is a job board aimed at eliminating the hassle of searching for design and development jobs. Find Bacon is a pleasant alternative to massive job boards like Indeed and Monster and is highly affordable. Posting a job posting for a 30-day period is only $99 dollars.

They also offer subscription packs which allow for 10 job posts a month. If you’re a company looking to fill multiple positions or are planning on hiring freelancers on an ongoing basis, you may want to consider investing in a subscription pack for a niche job board like Find Bacon.

14. X-Team

X-Team matches you with qualified WordPress developers who receive mentorship and educational resources just for being a part of X-Team. Like Toptal and Gun.io, they do the heavy lifting of hiring. You won’t be saddled with rifling through resumes or preparing personalized interview scripts.

The major downside of using X-Team is that, as their name suggests, they are adept at organizing teams. If you’re only looking to hire an individual WordPress developer, you’ll want to use a different matching service.

15. College Recruiter

If you’re looking to fill a part-time, entry-level position, or internship, College Recruiter is a good place to connect with college students and recent grads. This is an especially good option if you happen to constrained to a tight budget and have flexibility in terms of a hiring schedule.

Again, you’ll have to do the hard work of vetting and interviewing, but if interviewing hires doesn’t sound too daunting, College Recruiter is a good place to search for entry-level talent.

Honorable Mention: SimplyHired

SimplyHired is a large job board. It’s similar to big, general job boards like Indeed or Monster. The site comes with loads of resources from salary recommendations to hiring guides, and offers low prices for job listings. Like with Indeed and Monster, you’ll get a great deal of exposure. With over a billion job applications delivered, SimplyHired is a highly-respected job board worth investigating.

Begin exploring salary estimators, post within a network of over a hundred job boards in record time, and browse through the collated jobs by cities to see if posting a job listing on SimplyHired is worth the time and money for you.

Choosing the right site

Finding the best sites to find developers is no easy endeavor. Unless you’re a battle-worn recruiter, you likely won’t know how to navigate the complexities of hiring a WordPress developer. That’s completely okay—there’s plenty of sites and services to help you along the way.

Matching services like Toptal, and to a lesser extent, Codeable.io and Gun.io, are great solutions for employers searching for tech talent, and for those who are looking to place their trust in experienced tech professionals. For those short on time with high-quality developers as a priority, Toptal and Codeable.io are superb choices.

On the other end of the spectrum, there are freelance marketplaces like Upwork, People Per Hour, and Freelancer that allow you to cast a much wider net for WordPress developers.

Employers looking for full-time developers may also benefit from utilizing Stack Overflow and GitHub’s job boards, which can provide wonderful exposure to the WordPress developer community.

Specialized job boards like WPHired and WordPress Jobs can help you pinpoint the developers you need for your project.

Freelance marketplaces like Upwork, Freelancer, and Guru allow you to instantly connect with developers, but you’ll have to care for the hiring details yourself. If you have ample time to devote to screening candidates and are confident in your ability to interview WordPress developers, they are a great choice. Otherwise, you should steer clear from marketplaces and job boards alike.

Job boards, marketplaces, and matching services all have their uses. Which site will best serve you will depend on your specific situation.

Ultimately, which sites you employ depends on a multitude of factors, such as:

  • How quickly you need to hire a WordPress developer (i.e. your timeline)
  • How much experience you have hiring WordPress developers
  • Whether or not you’re equipped to test technical skills
  • How many developers you need to bring on
  • What level of experience those WordPress developers need
  • Whether or not you’re open to remote workers
  • What your budget constraints are
  • How important quality is to your project(s)

Source: DevelopersforHire.com

This article is written by Vivek Khatri and originally posted at Toptal

Magento development companies first heard the big Magento 2 announcement back in 2015. Was this just an evolutionary step in Magento development, as skeptics wondered at the time? Or was this the quiet beginning of a revolution that would sweep across the field of eCommerce?

Entrepreneurs the world over—examples include the likes of Oliver Sweeney and Devlyn—have been working with Magento agencies to build their eCommerce presence using Magento 2. The platform aims to provide new ways to enhance user engagement, make navigation smoother, and improve conversion rates and overall revenue generation for store owners.

Magento 2’s user-friendly infrastructure can be conveniently operated by its users. But this is also true for any Magento web developers, which is important when it comes to cost-effective Magento customization. The new Magento 2 framework allows developers to create new services for communicating with a Magento 2 store, which in turn allows a user to interact with the store across multiple devices.

Magento 2 is a highly improved and optimized version of the Magento platform. For example, Magento extension development has been made easier, so pluggable integration is standard fare for payment gateways like PayPal and Braintree or shipping methods like FedEx and DHL. In general, its improved toolchain is attracting Magento enterprise developers, and its remarkable speed is making it more appealing to end users as well.

And although some might say that it’s not quite as simple to install as Magento 1, I’ve personally found Magento 2 as simple to download and install on a server as Magento 1 is.

There are many other benefits that are helping convince eCommerce merchants and the Magento development companies they hire to make the switch; there’s no doubt that Magento 2 is a great achievement in the field of website development services. But as with many software packages, before you can benefit from it, there’s the important question of which edition is right for your business.

When Magento 2 was released, two editions were introduced:

  • Magento 2 Community Edition, which is now called Magento Open Source
  • Magento 2 Enterprise Edition, which is now called Magento Commerce

The differences between the two are definitely worth noting. But first, what they have in common may surprise you.

Magento 2 Community Edition is free, but nonetheless packed with features.

Magento Open Source (Formerly Magento 2 Community Edition)

As its new name implies, Magento Open Source is entirely free to download and use. And as you might have guessed, the paid edition, Magento Commerce, is built on top of the free edition and thus includes all of its features.

It may be free, but Magento Open Source is nothing to be scoffed at: Not only is it a great fit for many standard eCommerce businesses, even those needing some custom Magento development will find benefits in this foundational edition. Magento eCommerce developers are seeing a serious productivity boost with this version.

Extraordinary Architecture

Magento 2 provides an upgraded architecture which ultimately helps Magento eCommerce developers in creating the desired product–both the out-of-the-box functionality and any tweaks your specific shop may require.

Not only that, but the new architecture is more powerful, convenient, scalable, and customizable: It was created to solve the problems faced by developers working with the original Magento 1. As a happy side effect of this, Magento development has become more accessible to developers, which means there’s a wider talent pool covering all levels of Magento customization needs.

Open Source and Highly Customizable

This latest version of Magento benefits, of course, from all the standard perks of being open source: Security, quality, cost, and of course, customization. Your business can easily modify or extend Magento to suit its needs with the help of website development services—you’re not at the mercy of Magento, Inc. to decide whether or how to implement a desired feature.

Great Speed and Full-page Caching

Magento 2 gives 10 times better performance than the previous version due to a number of enhancements. Magento 2 allows sites to use full-page caching (FPC) on the server to display of all the significant categories, CMS pages, and products instantly. FPC thus reduces both server load and response time. Magento 2 has built-in FPC, but it also supports Varnish as an FPC back-end for even better performance.

Multiple Extensions and Mobile Friendly

Magento 2 is known for its powerful and easy-to-integrate extensions: Users can quickly install Magento extensions and/or themes as needed. Here are some examples of Magento 2 extensions, so you can see what sort of off-the-shelf functionality is available:

  • Special Promotions is used when running promotional campaigns. It lets you add various types of discount actions based on product price rankings, item quantities being ordered, and amounts being spent.
  • The SMTP Email Settings extension helps enable the use of a third-party email server for your Magento 2 website. (By default, Magento 2 normally uses your system’s built-in mail sender, which may be less reliable than a dedicated third-party solution.)
  • For businesses that also have a brick-and-mortar presence, Store Locator helps users find your nearest physical store via Google Maps.
  • The Amazon Pay extension makes it even easier for customers to pay on your site.

With such extensions available, you can see how rapidly Magento website development can progress. But a Magento development agency would have no trouble creating a new extension if you happened to need functionality not currently offered in the Magento marketplace.

Enhanced SEO

Several improvements were made to SEO in Magento 2 to help potential customers find your store more easily when searching the internet at large for products. Magento 2 provides separate meta tags for each category, product, CMS page, etc., which helps search engines know that your site is legitimate and boost its ranking.

The best new SEO feature provided in Magento 2 is the “Product Fields Auto-Generation” template, which helps define meta tags based on product information you’ve already entered. This saves a lot of time, making it painless to keep your store offering up-to-date—yet another reason why Magento development companies and their clients are attracted more toward Magento 2.

Multilingual and Multi-currency Support

The multilingual and multi-currency support provided by Magento 2 naturally help to create stores that support as wide a market as you care to target. But an interesting side effect of this support is that Magento development has gained even wider collaboration, leading to increased quality, security, etc.—in other words, multiplying the benefits of open-source software mentioned above.

Payment Gateways

Every eCommerce site needs its payment methods. Some gateways like PayPal are built into Magento 2, and others are available via extensions. No matter how you get there, Magento eCommerce development is very straightforward when it comes to secure and convenient third-party payment gateways.

Extremely Scalable and Highly Secure

One of the best-quality scaling solutions I’ve seen, Magento 2 automatically allows Magento web developers to resize or reframe websites according to their changing needs, making it adapt faster and continuing to provide a great user experience. In an age of ever-changing security concerns, Magento 2 was rewritten with security as a high priority, so site owners and their customers can be confident they’ll have a safe experience.

Enterprise Features Also Found in Magento Open Source

Magento Open Source, despite being the free edition, has some features that perhaps reach beyond what its typical user base may be interested in. They are included in Magento Commerce, just like all other Magento Open Source features.

Knockout JavaScript Framework

We’ve all used sites before where a mistake in the code results in inconsistent messages being sent to the user. Since Magento 2 leverages the Knockout library under the hood, it makes it a lot easier for developers to avoid such inconsistencies while they customize Magento, giving you confidence that your website will stay looking sleek and professional.

Command Line Interface (CLI)

Some Magento core functionality like installation, cache and index management, and user management can now, as of Magento 2, be done via a CLI. Normally, these tasks would need to be done periodically by an admin or a developer, but with CLI, a developer can schedule a script to do this automatically, saving the developer time and tedium and saving the store owner money.

Backup and Rollback System

Magento website developers and administrators alike know the pain of a poor backup system when something goes wrong. Magento 2’s new backup and rollback system helps in restoration of databases orfiles or media changes created earlier, as required. This feature offers an advantage in Magento development, as it limits any potential loss of data by providing more sophisticated backups.

Adaptable Content Management System (CMS)

This helps Magento eCommerce developers to implement the design you envision instead of being boxed into the preconceived notions of your platform. Robustness is a real advantage here: Magento’s CMS can be made to look “just right” for all of your products and processes while staying lean and efficient.

Magento Commerce covers some high-end features not found in Magento Open Source, but at the same time, some Magento Open Source features are more enterprise-ready than you might expect.

Magento Commerce (Formerly Magento 2 Enterprise Edition)

Large stores and eCommerce merchants are the biggest buyers of Magento Commerce because of their expandable businesses.

If the above features still aren’t enough for your business, Magento Commerce is what you need.

Advanced Analytics and Reporting

While Magento Open Source does provide some basic analytics and reporting features, Magento Commerce really shines here by allowing your Magento store development to integrate with Google Universal Analytics. This connects your Magento store to the following Google tools to help optimize your content, analyze your traffic and audience, and connect your catalog to various marketplaces:

  • Google Analytics allows you to define additional custom dimensions and metrics for tracking, with support for offline and mobile app interactions and access to ongoing updates for your website.
  • Google Tag Manager helps you boost and track your store’s SEO performance.
  • Google AdWords is used to place ads (and track their results) in Google Search results and other Google products.

Out of the box, Magento Commerce provides statistical reports on marketing, sales, review, customers, customer activity, products, and private sales. As always, if when your reporting needs go beyond the vast selection available, a Magento solution specialist will have no trouble filling the gap.

Split-database Solution

This is a scalability advantage which has the ability to use three separate master databases for different functional areas. Checkout, order, and product data each use a separate master database that can be replicated optionally with slave databases.

This separation scales the load independently among the website’s checkout functionality, order management activities, merchandising activities, and website browsing, depending on your needs. These changes provide flexibility in how the database tier can be scaled, which is a huge advancement in the field of Magento development.

Visual Merchandiser

This allows eCommerce Store’s merchandise managers to drag and drop web content to build customized product displays in preview mode and make adjustments before applying changes on the live site. Visual Merchandiser has made Magento eCommerce website development easier than ever with its sleek navigation through categories and products.

Advanced Search with Elasticsearch

Another beneficial feature of Magento Commerce, Elasticsearch performs quick and advanced searches on products in the catalog. With this, your customers will get exactly what they are looking for, and as a result, you will get higher conversion rates.

Because it returns search results based on the last generated index until the next complete reindexing cycle, there’s no disruption to customers. Plus, Elasticsearch analyzers support multiple languages, which gives a much better experience to users and Magento web developers as well.

Private Sales

Every business has its important and valuable customers for whom they enable exclusive shopping experiences, and Magento Commerce has this covered. Best offers, customer behavior, and prompt purchases are some of the basic factors of private sales. Their configurability is a yet another great benefit attracting more Magento agencies and entrepreneurs towards Magento 2.

Advanced Admin Roles

Magento 2 provides special functionality in Magento Commerce known as “advanced admin roles.” With this feature, you can restrict an administrative user’s access to only a specific website or store and its associated data.

If you have more than one brand, or business units with different stores on the same Magento installation, you can provide administrative access to each one of your business units while hiding and protecting their data from other administrators. Hence, administrators have exactly the amount of control you would want them to have within the whole system. This provides great security for your Magento development.

Automated Rules with Customer Segmentation and Attribute Management

Magento Commerce customer segments like customers, customer addresses, shopping carts, products, and sales are defined in a particular manner. Magento store developers can easily add new customer attributes and take them along to further sales and marketing processes as well as help with advanced personalization.

Gift Cards

Magento Commerce also lets you offer perks to attract customers, which directly increases the conversion rate of the site. Offering gift cards is a great strategy to grab your customers and get them to revisit your site to redeem those gift cards.

24×7 Technical and Account Support

Not a feature of the software itself, but nonetheless an important consideration for enterprise clients, Magento Commerce has 24×7 technical and account support available. Selling your products to customers is not sustainable until and unless you find a way to resolve any problems and issues arising from feedback; this support is a great advantage in Magento website development.

As of this writing, this is also true for Magento 1. But soon, that support will officially no longer be offered, at which point this will be available for Magento 2 only.

This article is written by Vivek Khatri and originally posted at Toptal

Finding the perfect site to hire PHP developers shouldn’t be as difficult as it is. If you have a minute, you should consider filling out Developers for Hire’s PHP questionnaire. On average, it takes only sixty seconds to complete. Answer a few of their questions and you’ll receive personalized results based on your needs. It’s an extremely useful tool if you’re lost on how to begin your talent search.

If you’d rather sort through this list, however, you can rely on the companies compiled below. In short, the best places on the web will let you have it all: you’ll have access to quality developers, and can expect expediency and consistency. This list attempts to compile those exceptional sites for finding developers.

Here are the top 15 places to find a PHP developer:

1. Toptal

Toptal is a matching service, initially created with only tech talent in mind. Although it has expanded its pool of talent to include designers and finance experts, the company’s bread and butter is its developer vertical. If you want to be sure that a PHP developer is up to the job, hiring an exceptional developer from Toptal is likely your best option.

Why? Toptal boasts an elite developer base. Their trademark system for vetting talent allows for only the best to become a part of their community. According to Toptal, only 3% of applicants make it through their battery of technical tests and their comprehensive vetting process.

2. Hired

Hired helps employers find software engineers and developers. On Hired, you can use their pipeline to find custom matches. You can create a company profile, search for candidates using their search algorithm (which can eliminate gender and racial identifiers for fairer hiring), and request interviews with candidates.

What’s best about Hired? It’s great for finding specialized PHP developers who are actively searching for new opportunities, have relevant experience (as most candidates on Hired have at least two years of experience), and are in your area.

3. We Work Remotely

We Work Remotely is a job board dedicated to remote listings. As a result of the remote-only restriction, there is a higher than average amount of tech and tech-creative hybrid job postings, which include front-end web developer and PHP developer positions.

Posting a job listing may be a bit more expensive than other job boards listed at $299 a month. However, if you’re looking to fill a remote position and are uninterested in recruiting local employees or freelancers, you should strongly consider utilizing We Work Remotely.

4. GitHub Jobs

Don’t waste your time perusing large job boards like Monster and Indeed. You’ll have far better luck with job boards geared toward tech talent. GitHub has a massive front-end developer community as it’s one of the largest open-source online repositories for coders. For a relatively small fee, you can post a PHP developer job listing and gain a great deal of exposure on GitHub’s huge developer community.

5. Authentic Jobs

Authentic Jobs is a job board for leading web, design, and creative talent. It has been steadily rising in popularity since its inception; the board is used by The New York Times in part of its acquisition process.

As PHP development often involves both creative and technical aspects, Authentic Jobs is a great place to begin your search. Much of their job board is populated with listings of web developer and PHP developer positions which are likely similar to your own needs.

Authentic Jobs allows for posting developer positions remote or local, so you won’t be limited to remote employees or freelancers.

6. Stack Overflow

Stack Overflow has an online community that rivals GitHub. Arguably, it’s the absolute largest and most trusted community of developers on the web. Stack Overflow is often used as a resource for all kinds of developers, novice to expert, seeking to learn more about coding. Their job board, like GitHub’s, allows for an incredible amount of exposure to dedicated PHP developers around the world.

7. Upwork

If recruiting services and job boards aren’t your thing, you might want to consider a freelance marketplace like Upwork.

Upwork has one of the largest marketplaces with millions of registered freelancers. You can hire contractors for a few simple coding tasks or begin a long-term relationship with a series of complex PHP projects. If you like the idea of finding, interviewing, and managing freelancers, Upwork’s colossal marketplace will likely meet your needs.

8. PHP Classes

Poor UI aside, PHP Classes is a wonderful site to find PHP developers for hire. The site has a large community of PHP developers, offers a wealth of educational resources, and offers forums and job boards. If you have a great deal of time on your hands, and don’t mind the site’s unintuitive look and feel, PHP Classes may be a good resource for hiring PHP developers.

9. People Per Hour

People Per Hour is another freelance marketplace akin to Upwork. What makes People Per Hour unique is that it holds contests and allows Freelancers to post their own job postings called hourlies.

People Per Hour has millions of members, thousands of confirmed hours, and a plethora of success stories from freelancers and entrepreneurs alike. The ease of posting jobs, contacting freelancers, and paying for hours worked makes People Per Hour a superb choice for employers interested in searching for and vetting freelance candidates themselves.

Additionally, with People Per Hour, you can connect with local freelancers, so you aren’t necessarily limited to remote talent.

10. Gun.io

Gun.io has a growing community of developers over 25,000 strong. Like Toptal, their service is designed to take the tedium out of hiring. Gun.io vets their talent and ensures that their freelancers are committed to each and every project.

Gun.io prides itself on its humanism. Every employer is connected with a VP, instead of a sales representative, and freelancers are provided with the resources to succeed.

What’s most alluring about the network? Gun.io manages and replaces talent – with no risk to you – and back every single hour worked with a 100% money-back guarantee.

11. Guru

Guru has a large global network of freelancers—albeit smaller than Upwork’s and People Per Hour’s massive pool. You can explore the profiles of 1.5 million gurus, propose projects, and pay your hired talent with their secure SafePay system.

Guru isn’t focused on developers, let alone Angular developers, as it is a freelance network comprised of every sort of professional. So, like with Upwork and People Per Hour, you’ll have to narrow your search yourself. Also, as with many freelance networks, the vetting and interviewing will be up to you.

12. Freelancer

Freelancer is an absolutely massive marketplace with 25 million registered users, 12 million total posted jobs, and thousands of completed projects. With the size of the marketplace comes a unique challenge, however. Finding the perfect PHP developer is like finding a needle in a haystack.

Although website development is one of the most popular job categories on Freelancer, you still will have to search through thousands of Freelancer profiles, vet and interview candidates yourself, and manage payments yourself.

If you’re looking for an affordable option, however, Freelancer is a wonderful hiring solution. For more long-term commitments, you’ll want to consider matching services like Toptal or Gun.io.

13. Find Bacon

Find Bacon is a job board aimed at eliminating the hassle of searching for design and development jobs. Find Bacon is a pleasantalternative to massive job boards like Indeed and Monster and is highly affordable. Posting a job posting for a 30-day period is only $99 dollars.

They also offer subscription packs which allow for 10 job posts a month. If you’re a company looking to fill multiple positions or are planning on hiring freelancers on an ongoing basis, you may want to consider investing in a subscription pack for a niche job boardlike Find Bacon.

14. X-Team

X-Team matches you with qualified PHP developers who receive mentorship and educational resources just for being a part of X-Team. Like Toptal and Gun.io, they do the heavy lifting of hiring. You won’t be saddled with rifling through resumes or preparing personalized interview scripts.

The major downside of using X-Team is that, as their name suggests, they are adept at organizing teams. If you’re only looking to hire an individual PHP developer, you’ll want to use a different matching service.

15. College Recruiter

If you’re looking to fill a part-time, entry-level position, or internship, College Recruiter is a good place to connect with college students and recent grads. This is an especially good option if you happen to constrained to a tight budget and have flexibility in terms of a hiring schedule.

Again, you’ll have to do the hard work of vetting and interviewing, but if interviewing hires doesn’t sound too daunting, College Recruiter is a good place to search for entry-level talent.

Honorable Mention: SimplyHired

SimplyHired is a large job board. It’s similar to big, general job boards like Indeed or Monster. The site comes with loads of resources from salary recommendations to hiring guides, and offers low prices for job listings. Like with Indeed and Monster, you’ll get a great deal of exposure. With over a billion job applications delivered, SimplyHired is a highly-respected job board worth investigating.

Begin exploring salary estimators, post within a network of over a hundred job boards in record time, and browse through the collated jobs by cities to see if posting a job listing on SimplyHired is worth the time and money for you.

Choosing the right site

Finding the best sites to find developers is no easy endeavor. Unless you’re a battle-worn recruiter, you likely won’t know how to navigate the complexities of hiring a PHP developer. That’s completely okay—there’s plenty of sites and services to help you along the way.

Matching services like Toptal, and to a lesser extent, Hired and Gun.io, are great solutions for employers searching for tech talent, and for those who are looking to place their trust in experienced tech professionals. For those short on time with high-quality developers as a priority, Toptal and Hired are superb choices.

On the other end of the spectrum, there are freelance marketplaces like Upwork, People Per Hour, and Freelancer that allow you to cast a much wider net for PHP developers.

Employers looking for full-time developers may also benefit from utilizing Stack Overflow and GitHub’s job boards, which can provide wonderful exposure to the PHP developer community.

Freelance marketplaces like Upwork, Freelancer, and Guru allow you to instantly connect with developers, but you’ll have to care for the hiring details yourself. If you have ample time to devote to screening candidates and are confident in your ability to interview PHP developers, they are a great choice. Otherwise, you should steer clear from marketplaces and job boards alike.

Job boards, marketplaces, and matching services all have their uses. Which site will best serve you will depend on your specific situation.

Ultimately, which sites you employ depends on a multitude of factors, such as:

  • How quickly you need to hire a PHP developer (i.e. your timeline)
  • How much experience you have hiring PHP developers
  • Whether or not you’re equipped to test technical skills
  • How many developers you need to bring on
  • What level of experience those PHP developers need
  • Whether or not you’re open to remote workers
  • What your budget constraints are
  • How important quality is to your project(s)

Source: Developersforhire.com