Develop House-Ads API With Laravel For Mobile App/Game

Richard Fu
Sweaty Chair
Published in
6 min readSep 11, 2021
Interstitial popup to cross promo another app, by Sweaty Chair. Image mockup from www.freepik.com.

You’re probably familiar with house ads (or cross-promotion ads) in mobile games. Famous game publishers like Vodoo use house ads extensively to cross-promo their products. While you can put the house ads locally and pre-built them in the apps, having a server to serve the API and assets can give you live control of the ads. You can also use this as app event notices, to gives dynamic information and interaction with your users.

This article will walk you through how to create the API calls with Laravel step-by-step.

TLDR; You can review the code or simply install my Laravel House Ads package.

Overview

We are aiming to design a generic API, that allows clients to serve the house ads in different formats with the corresponding analytic data. The two most used cases are:

  1. UI Box
  2. Interstitial Popup

UI Box

The most used house ads type that is used by most game publishers, usually a GIF/video playing in the menu and do not interfere with the users. For analytics, we may be interested in how many times an ad shows and how many times it has clicked.

Interstitial Popup

A popup that usually shows in app-launch. It may contain a cancel button to go back to the app, and a confirm button to link to the cross-promo app or webpage. The interstitial popup can also be used as an in-app notification for anything you want to inform the users, such as version-upgrade, offline events, etc. For analytics, we are interested in how many times an ad shows, cancel and confirm button are clicked.

Database migrations

By using Laravel’s database migrations, the database setup is pretty simple, we only need 1 database:

House Ads Table

php artisan make:migration create_house_ads_table

/database/migrations/{datetime}_create_house_ads_table.php:

<?phpuse Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
class CreateHouseAdsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('house_ads', function(Blueprint $table) {
$table->id();
$table->integer('game_id')->unsigned();
$table->string('media_portrait', 128)->nullable();
$table->string('media_landscape', 128)->nullable();
$table->boolean('open_url')->default(true);
$table->string('url_ios', 256)->nullable();
$table->string('url_android', 256)->nullable();
$table->tinyInteger('repeat_count')->unsigned()->default('1');
$table->tinyInteger('priority')->unsigned()->default('1');
$table->date('start_at');
$table->date('end_at');
$table->mediumInteger('shown_count')->unsigned()->default('0');
$table->mediumInteger('confirmed_count')->unsigned()->default('0');
$table->mediumInteger('cancelled_count')->unsigned()->default('0');
$table->timestamps(); $table->foreign('game_id')->references('id')->on('games')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('house_ads');
}
}
  • game_id — which game/app this house ad redirect to. This is used in client app to distingish and not showing its own house ad.
  • media_portrait — the portrait image/video filename. The media should be uploaded and stored in <server root>/media folder. You can also use video and implement the playing function in client app.
  • media_landscape — the landscape image/video filename.
  • open_url — should the house ad open and URL, otherwise the client should simply show the image/video. This is mostly used for showing an event image, cutscene video, etc.
  • url_ios — the URL to open in iOS devices.
  • url_android — the URL to open in Android devices.
  • repeat_count — how many app launches to wait to show this ad again, for interstitial popup only.
  • priority — the highest priority ad get shown in one app launch.
  • start_at — the date that this house ad start, used this to schedule future house ads.
  • end_at — the date that this house ad end, used this to end promotion with a given period.
  • shown_count — the shown count, used for analytics only.
  • confirmed_count — the confirmed count (successful redirect), used for analytics only.
  • cancelled_count — The cancelled count (failed redirect), used for analytics only.

Note that we use the Game Essentials package here for basic game database setup, which will not be covered in this article.

Models

Then, we simply map the database table as its own Laravel Eloquent model. Again, we only need one model here:

House Ad model

php artisan make:model HouseAd

/app/Models/HouseAd.php:

<?phpnamespace Furic\HouseAds\Models;use Illuminate\Database\Eloquent\Model;
use Furic\GameEssentials\Models\Game;
class HouseAd extends Model
{
protected $guarded = []; protected $hidden = ['game_id', 'media_portrait', 'media_landscape', 'confirmed_count', 'cancelled_count', 'start_at', 'end_at', 'created_at', 'updated_at']; protected $appends = ['url_media_portrait', 'url_media_landscape', 'game']; public function getUrlMediaPortraitAttribute()
{
return url('/media/'.$this->media_portrait);
}
public function getUrlMediaLandscapeAttribute()
{
return url('/media/'.$this->media_landscape);
}
public function game()
{
return $this->belongsTo(Game::class);
}
public function getGameAttribute()
{
return $this->game()->first();
}
}

HouseAd has belongsTo relationship to a game model, and contains the addictive media URL fields.

Routing

We simply have the API route in Laravel Routing. You may want to add Web route so for the admin console to manage the house ads, but again not covered here.

API route

In /routes/api.php, add:

<?phpuse Illuminate\Support\Facades\Route;
use Furic\HouseAds\Http\Controllers\HouseAdController;
Route::prefix('api')->group(function() { Route::resource('house-ads', HouseAdController::class)->only([
'index', 'show', 'update'
]);
});

This creates API routes as {api-url}/house-ads and {api-url}/house-ads/{id} for your client app to checking the current house ads and update specific one for analytics.

Controllers

Finally, we will have one Laravel Controller:

HouseAdController

php artisan make:controller HouseAdController

/Http/Controllers/HouseAdController.php:

<?phpnamespace Furic\HouseAds\Http\Controllers;use Furic\HouseAds\Models\HouseAd;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Validator;
class HouseAdController extends Controller
{
/**
* Display a listing of the house ad resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return response(HouseAd::whereDate('start_at', '<=', date('Y-m-d'))->whereDate('end_at', '>=', date('Y-m-d'))->orderBy('priority', 'desc')->get(), 200);
}
/**
* Display the specified house ad resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
try {
return response(HouseAd::findOrFail($id), 200);
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
return response([
'error' => 'No house ad found.'
], 400);
}
}
/**
* Update the specified house ad resource in storage.
*
* @param Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
$validator = Validator::make($request->all(), [
'confirmed' => 'sometimes|required|numeric',
'shown' => 'sometimes|required|numeric',
]);
if ($validator->fails()) {
return response([
'error' => 'Key "confirmed" required.'
], 400);
}
try {
$houseAd = HouseAd::findOrFail($id);
if ($request->has('confirmed')) {
if ($request->confirmed == '1') {
$houseAd->confirmed_count++;
} else {
$houseAd->cancelled_count++;
}
}
if ($request->has('shown')) {
$houseAd->shown_count++;
}
$houseAd->save();
return response($houseAd, 200);
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
return response([
'error' => 'No house ad found.'
], 400);
}
}
}

In HouseAdConstroller, we only index(), show() and update().

  • index() - listing the house ads which current datetime is within their start_at and end_at.
  • show() - showing a specific house ad, for debug only.
  • update() - updating the analytics count of a given house ad.

Conclusion

That’s it! We’ve set up a simple API call to list and update house ads. Now simply add the database entries and their corresponding media file in <server root>/media folder, it should be ready to go!

Again, you can read all code in the GitHub repo or simply install the package using composer, while the project is still pretty simple and there’s few TODOs.

Oh, that’s the server back-end part, I will write another post on how to call the API and handle the response in Unity later. 😀

Lastly, leave a comment if you got any questions and wish this article and the package may help you.

Please follow me and Sweaty Chair for more latest game dev updates.

--

--

Richard Fu
Sweaty Chair

Frontend Developer at Twist, Easygo. Ex-cofounder at Sweaty Chair and Block42. Hit me up for any game idea and cooperative opportunities. 🎮