Popular Photos, Filters and User Profiles with the 500px API

Younes Rafie

500px is a photo community for discovering, sharing, buying and selling inspiring photography. In this article we are going to explore their API and build a small showcase app. Let’s get started.


What We’re Building

In this article, we are going to build a small Laravel app with three main features. You can check the final result on Github.

  • The index page will contain the latest popular photos on the 500px website.
  • A filter will let the user choose from popular, upcoming, etc. and also sort the result according to votes, rating, etc.
  • A photographer’s profile page with the list of available photos will be available.

Setting up

Before using their API, we need to get some access credentials. After signing in to the website you can go to the application settings and register for a new test application. The consumer key and the consumer secret will be used for accessing the API.

We are going to use Guzzle to make HTTP requests, and Guzzle Oauth subscriber for using OAuth. If you are not familiar with these two, you can check out this Guzzle introduction and read about using Guzzle OAuth.

Let’s first add the Guzzle packages to our composer.json and autoload our src folder where we put our authentication class. Run composer update to update our app.

// composer.json
"require": {
      "guzzle/guzzle": "3.9.*",
      "guzzlehttp/oauth-subscriber": "0.2.*"
"autoload": {
	"classmap": [

Inside our src/PxOAuth.php file we are going to provide our consumer_key and consumer_secret to build our OAuth object.


class PxOAuth{

    public function __construct($host, $consumer_key, $consumer_secret){
        $this->consumer_key = $consumer_key;
        $this->consumer_secret = $consumer_secret;
        $this->host = $host;

        $oauth = new Oauth1([
            'consumer_key'      => $this->consumer_key,
            'consumer_secret'   => $this->consumer_secret

        $this->client = new Client([ 'base_url' => $this->host, 'defaults' => ['auth' => 'oauth']]);

To take advantage of Laravel’s IoC we can bind our class to the container so it can be resolved automatically.

// bootstrap/start.php

App::singleton('pxoauth', function(){
    $consumer_key = 'TxNYEWxvU26cylAkxTc1KgNmXCPvFc1EazhIk5Po';
    $consumer_secret = 'n88vhgVgpkaCr3I0h1yB1bmkhy72jPzhhzFSbpYI';
    $host = 'https://api.500px.com/v1/';

    $oauth = new PxOAuth($host, $consumer_key, $consumer_secret);

    return $oauth;

We bound the class as a singleton because we only need to resolve it once and always return the same instance after that.

Our index route will retrieve today’s popular photos and supports a number of parameters. You can check the docs for the list of available options.

// app/routes.php
Route::get('/', ['uses' => 'PXController@index']);
// controllers/PXController.php
public function index(){
    $filters = [
        'feature'           => Input::get('feature', 'popular'),
        'sort'              => Input::get('sort', 'created_at'),
        'sort_direction'    => Input::get('sort_direction', 'desc'),
        'page'              => Input::get('page', 1)

    $result = $this->loadPhotos($filters);

    return View::make('index', ['photos' => $result['photos'], "inputs" => $filters]);

private function loadPhotos($parameters){
    $parameters = array_merge(['image_size' => 3], $parameters);

    $px = App::make('pxoauth');
    $result = $px->get('photos', $parameters)->json();

    return $result;


We can filter the list of photos by category and sort them according to a specific set. You can check the docs for the list of supported parameters. The API returns the list of photos with some other useful properties. The images property inside a single photo item contains an array of photos depending on the requested size. You can choose from 1 to 4, or pass in an array of sizes.

// views/index.blade.php


<div class="row photos">
    <div class="col-lg-12">
        <h1 class="page-header">Popular photos</h1>
    <form action="/" method="GET" id="filters">
      // filter options
    @include('partials/photos', ['photos' => $photos])

    <button id="load_more" class="btn btn-primary load_more center-block">Load more</button>


We include a photos partial and the pagination button to illustrate the use of the page query parameter.

// views/partials/photos.blade.php

@foreach($photos as $photo)
    <div class="col-lg-3 col-md-4 col-xs-6 thumb" data-photo-id="{{ $photo['id'] }}">
        <a class="thumbnail" target="_blank" href="http://500px.com{{ $photo['url'] }}">
            <img class="img-responsive" src="{{ $photo['images'][0]['url'] }}" alt="{{ $photo['name'] }}">
        <div class="caption">
            <a class="pull-left" href="/user/{{ $photo['user']['id'] }}">{{ $photo['user']['fullname'] }}</a>
            <a class="pull-right fa fa-heart favorite" href="#"> {{ $photo['favorites_count'] }}</a>
            <a class="pull-right fa fa-thumbs-up vote" href="#"> {{ $photo['votes_count'] }}</a>


Photos Pagination

The load more button will fire an ajax request to get the next page and append it to the current page.

// views/index.blade.php

            var page = $(this).data('page') || 2,
                filter_form = $("#filters"),
                feature = filter_form.find("select[name='feature']").val(),
                sort = filter_form.find("select[name='sort']").val(),
                sort_direction = filter_form.find("select[name='sort_direction']").val();


                url: '/ajax/index_more',
                data: {
                    page: page,
                    feature: feature,
                    sort: sort,
                    sort_direction: sort_direction
                type: 'get',
                success: function(data){
                    var photos = $('.photos'),
                        more_photos = $(data);


                    $('#load_more').data('page', page + 1);
                $("#load_more").text("Load more");
// app/routes.php
Route::get('/ajax/index_more', ['uses' => 'PXController@loadMore']);
// controllers/PXController.php
public function loadMore(){
    $filters = [
        'feature'           => Input::get('feature', 'popular'),
        'sort'              => Input::get('sort', 'created_at'),
        'sort_direction'    => Input::get('sort_direction', 'desc'),
        'page'              => Input::get('page', 1)
    $result = $this->loadPhotos($filters);

    return View::make('partials/photos', ['photos' => $result['photos']]);

After a successful request, we pass the list of photos to the partial and send the result back to the page. We also increment the current page indicator.

User Profile

Every photo has an owner, so when the user selects a photographer we filter photos by the user id.

// app/routes.php

Route::get('/user/{id}', ['uses' => 'PXController@photosByUser']);

The API provides a users/show endpoint to retrieve a specific user using the user id, username or email. After that, we query the previous photos endpoint with some extra parameters.

// controllers/PXController.php

public function photosByUser($uid){
    $px = App::make('pxoauth');

    $user = $px->get('users/show', ['id' => $uid])->json();
    $inputs = ['image_size' => 3, 'feature' => 'user', 'user_id' => $uid, 'rpp' => 100];
    $result = $this->loadPhotos($inputs);

    return View::make('user', ['photos' => $result['photos'], 'user' => $user['user']]);

User photos

The API also provides a set of endpoints to search for a user using some term, list followers and friends. You can check the docs for the list of endpoints.

We can also create new functionality by consuming more API endpoints. For example, when the user selects a photo we can display the list of comments (photos/:id/comments) and maybe let the user submit a new comment. We can also hide adult content when the nsfw attribute is set to true.


In this article we covered some basics of the 500px API, in the next part we are going to build more functionality and try to extend our application. You can check the Github repository to test the current version of our app. Let me know what you think in the comments below.