Working with Laravel + AngularJS - part 2
November 30, 2014 workflow laravel
Last time we saw a couple of different options on how to integrate Angular
and Laravel
. Today we are going to learn how to create our backend and frontend as separate applications but having them work together under the same domain. Every request will go to angular except the ones for /api
which are going to be managed by Laravel
.
First, we need to create our applications:
$ cd /path/to/your/apps
$ mkdir sharingdomain && cd $_
$ laravel new sharingdomain-backend
$ git clone https://github.com/Foxandxss/fox-angular-gulp-workflow sharingdomain-frontend
So in a new folder, we created a new laravel application and also we cloned an angular boilerplate (I used mine, but you can use anything else, or even your own boilerplate).
Let’s git it:
$ rm -rf sharingdomain-frontend/.git
$ git init
$ git add .
$ git commit -m "First commit"
$ git remote add origin https://github.com/yourhandle/sharingdomain.git
$ git push origin master
On a real project, you would want to create a separate repository for each application to have the maximum flexibility, but for this demo, we are going to put both applications together on the same repository.
Like we did on the last article, we are going to create a controller to show us our favorite tv shows:
File: app/controllers/ShowController.php
<?php
class ShowController extends \BaseController
{
/**
* Display a listing of the shows.
*
* @return Response::json
*/
public function index()
{
$shows = array('Doctor Who', 'Stargate SG1', 'Once upon a time',
'The Blacklist', 'Prison Break', 'White Collar');
return Response::json($shows);
}
}
And we also need a router for it:
File: app/routes.php
Route::group(array('prefix' => 'api'), function () {
Route::get('shows', 'ShowController@index');
});
We made a group to prefix every route with /api
because as we said earlier, only the petitions to /api
are going to be managed by Laravel
.
Alright, let’s try our API, but first let’s run our laravel application:
$ cd sharingdomain-backend
$ php artisan serve --port 8000
Now with our app running on port 8000
we can try our api:
The Laravel
side is done. Notice how it knows nothing about Angular
or any kind of frontend.
Let’s commit it:
$ git add .
$ git commit -m "Laravel API done"
$ git push origin master
Let’s move to the Angular
application. First we need to install the dependencies:
$ cd ../sharingdomain-frontend
$ npm install
This will install all of our workflow dependencies and also install the bower
packages.
First, we are going to install angular-route
:
$ bower install --save angular-route
Next, we modify our vendor manifest like:
File: vendor/manifest.js
exports.javascript = [
'vendor/angular/angular.js',
'vendor/angular-route/angular-route.js',
'vendor/lodash/dist/lodash.js'
];
This makes our workflow aware of what vendor files we want to load and in what order.
Now we load ngRoute
as a dependency on our application:
File: app/js/app.js
angular.module('app', ['ngRoute']);
And then we configure it a little bit:
File: app/js/config.routes.js
angular.module('app').config(function($routeProvider, $locationProvider) {
$routeProvider.otherwise('/shows');
$locationProvider.html5Mode(true).hashPrefix('!');
});
That will redirect unknown routes to /show
and it will also enable the html5mode
in our application (In other words, no more /#/
in our URLs).
Then as a last step to enable that html5mode
we need to update our index.html
<head>
tag like:
File: app/index.html
<head>
<meta charset="UTF-8">
<title>Angular App</title>
<link rel="stylesheet" href="<%= css %>">
<base href="/" />
</head>
We just added a base
tag to it which is needed if we want to remove that hash on our routes.
Now, let’s code our feature:
File: app/js/features/shows/config.routes.js
angular.module('app').config(function($routeProvider) {
$routeProvider.when('/shows', {
templateUrl: 'features/shows/shows.tpl.html',
controller: 'Shows'
});
});
File: app/js/features/shows/shows.js
angular.module('app').controller('Shows', function($scope, $http) {
$http.get('/api/shows').then(function(result) {
$scope.shows = result.data;
});
});
File: app/js/features/shows/shows.tpl.html
<h1>My favorite TV Shows ever</h1>
<ul>
<li ng-repeat="show in shows">
{{show}}
</li>
</ul>
Lastly, we need to put an ng-view
on our index.html
which is the entry point of the router:
File: app/index.html
<body>
<div ng-view></div>
<script type="text/javascript" src="<%= js %>"></script>
</body>
We have our feature there, but if we look closer, we see that our request URL is /api/shows
. How can angular relates that URL with our Laravel
application running at port 8000
? It can’t do that by default but also, we don’t want to use CORS because in production we are not going to use that. So… what’s the solution here? We can proxy our requests for development. The workflow we are using does that for us automatically. If we open the gulpfile.js
we can see:
File: gulpfile.js
gulp.task('webserver', ['indexHtml-dev', 'images-dev'], function() {
plugins.connect.server({
root: paths.tmpFolder,
port: 5000,
livereload: true,
middleware: function(connect, o) {
return [ (function() {
var url = require('url');
var proxy = require('proxy-middleware');
var options = url.parse('http://localhost:8080/api');
options.route = '/api';
return proxy(options);
})(), historyApiFallback ];
}
});
});
That is the task that serves our angular application, and if you look carefully, you will see that we are building a proxy there. That means that by default, all the request we do to /api
are going to be redirected to localhost:8080/api
. Just what we needed!
The only change we need to do here, is to change the port from 8080
to 8000
to match the one we are using for Laravel
.
File: gulpfile.js
var options = url.parse('http://localhost:8000/api');
Alright, after all this coding, let’s run our angular application:
$ gulp
And if we have our Laravel
app running, we can see:
Let’s commit the changes:
$ git add .
$ git commit -m "Angular side done"
$ git push origin master
So, we have two separate projects now, one with Laravel
serving an API
and one with Angular
which is proxying all the /api
requests to Laravel
. Right, but how can we deploy this on the same domain but still have them separated? That is managed by nginx
. But first, we generate or production files for the Angular
app like:
$ gulp production
That will generate a dist
folder with the static files we will use on production.
Having that files and also our Laravel
application, we can move them to our server and then create an nginx
configuration like:
server {
server_name ourappname.local;
root /path/to/frontend/;
location / {
index index.html;
try_files $uri $uri/ /index.html =404;
}
location /api {
root /path/to/laravel/backend/public;
try_files /index.php =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_param APP_ENV dev;
fastcgi_pass 127.0.0.1:9000;
}
}
First, we set the path to our frontend (AKA the content of the dist
folder we generated previously) and then we set the path for the public
folder of our Laravel
application.
Thanks to this config, when we request /shows
that will go to Angular
but if we do any request that begins with /api
, that will go directly to Laravel
.
We can see it working here in production mode:
So here we saw another way to integrate Angular
with Laravel
. This idea is so cool because we can easily change the frontend or the backend without having to change any code from the other unchanged part.