This repository dedicated to learning Laravel, a PHP web application framework. This repository is designed to help me improve my skills and understanding of the Laravel framework.
In this repository, one can find a variety of resources including:
- Code snippets and examples.
- Practice projects.
- Learning notes.
Text Editor used is VSCode with the following extensions:
- To install php on linux:
sudo apt install php8.1-cli
- To create a local server with php run:
php -S localhost:8000>This is only for development purposes.
- Composer is a PHP dependency manager. To install composer on linux:
sudo apt install composer- Composer will place dependencies on the 'vendor' folder.
- Install slugify
composer require cocur/slugify- To import on a file use:
-
require __DIR__ . '/vendor/autoload.php'; use Cocur\Slugify\Slugify;
-
- To better understand autoloading and namespaces in PHP
- To create a Laravel project (using composer) run:
composer create-project laravel/laravel firstapp- This will create a new folder called 'firstapp' with the Laravel project.
- To start a local server for this project, from its root, run:
php artisan serve
The /about route will return the html code, and will be accessible from the browser by typing: localhost:8000/about.
Route::get('/', function () {
return view('welcome');
});
Route::get('/about', function () {
return '<h1>About Page</h1><a href="/">Back to home</a>';
});- To create a controller, run:
php artisan make:controller ExampleController
- Reference a controller method in a route:
Route::get('/', [ExampleController::class,"homePage"]);
A view is a file that contains HTML code. It is used to display data to the user.
- To create a view, just create a file in the
./resources/viewsfolder. The name of the file must end with.blade.php.homePage.blade.php
- Inside this file, only HTML code is required, but can also use php expresions. This expresions must be enclosed in double curly braces
{{}}.-
<p>sum of numbers {{ 2 + 2 }}</p> <p>The current year is {{ date('Y') }}</p>
-
- To pass data to a view.
public function homePage() { //to pass data to view from controller $myName = 'Steeven L'; return view( 'homepage', ['name' => $myName] ); }
- To read data from the function on the controller, inside curly braces.
<p>My name is {{ $name }}</p>
-
Using the include directive,
@include('').@include('header')
-
Using
{{slot}}to define a placeholder for the content.-
/views/components/layout.blade.php
<header>...</header> {{slot}} <footer>...</footer>
-
/views/homepage.blade.php
- x-name_of_file
<x-layout> <div>... homepage content ...</div> </x-layout>
-
Edit the .env file to add the database credentials.
- Create a route, that uses the POST method.
Route::post('/register', [UserController::class, "register"]);
- The form action must be the same as the first argument of the post route.
<form action="/register" method="POST" id="registration-form">
- The
@csfrdirective is used to prevent cross-site request forgery. It goes just afgter the openg form tag.
- The
- To handle the input of the user, a controller may be created. It is taken from the
Requestobject passed in. Rules can also be set to validate the input.$inputData = $request->validate([ 'username' => ['required', 'min:3', 'max:20', Rule::unique('users', 'username'),], 'email' => ['required', 'email', Rule::unique('users', 'email'),], 'password' => ['required', 'min:8', 'confirmed',], ]);
- To show the validation messages on the view, the
@errordirective is required. This example code will appear on the view if the validation fails, especifically for the username. It may be placed directly below the input associated with the error.@error('username') <p class="m-0 small alert alert-danger shadow-sm">{{ $message }}</p> @enderror
- To avoid, the user from having to re-type the already submitted data when an error occurs, the property 'value' can be set on the input tag. This does not work for the password input.
<input value="{{old('username')}}" name="username" id="username-register" class="form-control" type="text" placeholder="Pick a username" autocomplete="off" />
- The password can be easily hashed with the method
bcrypt()included in Laravel.$inputData['password'] = bcrypt($inputData['password']);
- To send the validated data to the database, the
create()method is used.User::create($inputData);
![]() |
|---|
![]() |
- A login route needs to be created.
Route::post('/login', [UserController::class, "login"]);
- A login() method on the UserController must be created to validate the input data, check the universal auth object, and if the authentication succeeds, create a cookie, to let know laravel that a user has logged in.
public function login(Request $request) { $inputData = $request->validate([ 'loginusername' => ['required',], 'loginpassword' => ['required',], ]); if (auth()->attempt([ 'username' => $inputData['loginusername'], 'password' => $inputData['loginpassword'], ],)) { $request->session()->regenerate(); return 'congrats'; } else { return 'failed'; } }
-
A logout route needs to be created.
Route::post('/logout', [UserController::class, "logout"]);
-
The logout() method on the UserController must be created to call logout on auth(). It can also redirect to the home page.
public function logout(Request $request) { auth()->logout(); return redirect('/')->with('success', 'You have been logged out.'); }
-
When calling with() on the redirect method, the message can be accessed on the view.
@if (session('success')) <div class="alert alert-success"> {{ session('success') }} </div> @endif
-
run
php artisan make:migration create_posts_table -
Modify the migration file to add the columns.
public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->timestamps(); $table->string('title'); $table->longText('body'); $table->foreignId('user_id')->constrained(); }); }
-
run
php artisan migrateto actually create the table on the database.
- Run
php artisan make:model Post - On the Post model class, under
use HasFactory, add:protected $fillable = ['title', 'body', 'user_id'];, so that it can recognize the data on the array that will be passed to the create() method.
- On the BolgController, add a method to create a post. The user_id can be extracted from the auth() object.
public function storePost(Request $request) { $postFields = $request->validate([ 'title' => 'required', 'body' => 'required' ]); $postFields['title'] = strip_tags($postFields['title']); $postFields['body'] = strip_tags($postFields['body']); $postFields['user_id'] = auth()->id(); Post::create($postFields); }
![]() |
|---|
![]() |
-
To create a dynamic url, the route must be created with a parameter. Thi
Route::get('/post/{post}', [BlogController::class, "viewPost"]);
-
The viewPost() method need to be created on the BlogController. The parameter passed to the method must match the parameter on the route, and a type must be also provided for the parameter, in this way Laravel can do argument hinting and automatically retrieve the post from the database.
public function viewPost(Post $post) { return view("single-post", ['post' => $post]); }
-
On the view, the post can be accessed with the variable passed to the view.
<h2>{{ $post->title }}</h2> -
To retrieve models linked with a foreign key, a method can be created on the Post model.
public function userFromPost(){ return $this->belongsTo(User::class, 'user_id'); }
-
It can be used in the view, through the post variable passed to the view.
Posted by <a href="#">{{ $post->userFromPost->username }}</a> -
After the post creation, a redirection can be issued, to send the user to the newly created post.
$newPost = Post::create($postFields); return redirect("/post/{$newPost->id}")-> with('success', "New post created successfully");
- Override the body content on the post, importing Str, and using the built-in markdown() method.
$post['body'] = Str::markdown($post->body);
- On the view, it es needed to allow html tags.
<p>{!! $post->body !!}</p>
It allows to check the state of the app, to guard routes or other resources from unauthorized access. it is a layer that can modify the request or response, or even block the request.
- To create custom middleware, run:
php artisan make:middleware customActionMiddleware - To use a middleware, it must be registered on the
app/Http/Kernel.phpfile. - To apply middleware, on tho routes, it must be called on the route object.
Route::post('/login', [UserController::class, "login"])->middleware('guest'); Route::post('/logout', [UserController::class, "logout"])->middleware('auth');
The general idea for this is, only the author should be allowed to modify or delete the post, others should not see or access that functionality.
- To create a policy, run:
php artisan make:policy PostPolicy --model=Post, using the model flag, will allow Laravel to create extra boilerplate code, that will "link" the policy with the Post model. - Policies are registered on the
app/Providers/AuthServiceProvider.phpfile.protected $policies = [ Post::class => PostPolicy::class, // 'App\Models\Model' => 'App\Policies\ModelPolicy', ];
- Policies will have several methods, that need to be modified to return a boolean value, based on on the rule the policy is based on.
public function update(User $user, Post $post) { return $user->id === $post->user_id; }
- Middleware can also be used to enforce a policy. For example to onle allow deletion of posts by their respectivo authors.
Route::delete('/post/{post}', [BlogController::class, "deletePost"])->middleware('can:delete,post');
- To use the policy on the view, the
@candirective may be used.@can('update', $post) <a href="/delete" method="POST">Delete post</a> @endcan
-
To modify an already existing table, using migration, run:
php artisan make:migration add_isadmin_users_table --table=users -
Then on the
up()method, instruct artisan to create the column$table->boolean('isAdmin')->default(false);
The
down()method can contain the code to reverse what is on theup()method.$table->dropColumn('isAdmin');
-
Run
php artisan migrateto apply the changes to the database.
-
Modify post policy to take into account the isAdmin status. this con be set up for both, the update() and delete() methods.
public function update(User $user, Post $post) { return $user->id === $post->user_id || $user->isAdmin; }
Gates are a way to check if a user has a specific permission. They are not linked to a model, and can be used to check if a user has a specific permission, or if a user is an admin.
- To define a policy, on the
app/Providers/AuthServiceProviderclass, theGate::define()method can be used inside theboot()method.Gate::define('accessAdminPage', function (User $user) { return $user->isAdmin; });
- This can be used on the routes file, to protect the routes that need to be "gated".
Route::get( '/admin', [UserController::class, "adminPage"], )->middleware('can:accessAdminPage');
- On the user controller class, a method to show the route must be created.
- To allow file uploads, the
enctype="multipart/form-data"attribute must be added to the form.<form action="/post" method="POST" enctype="multipart/form-data">
- To allow the upload of files bigger than 2MB, it is neccesary to edit the php.ini file, and change the
upload_max_filesizevalue. - Create a post route to upload the file, and a function on the controller, to configure the upload. Here the file can be validated, for example make sure is an image, or smaller that a certain size.
public function storeAvatar(Request $request){ $request->validate([ 'avatar' => ['required|image|max:3000',], ]); //file tag name, is the name used to retrieve that specific file. <input name="avatar"... $request->file('avatar')->store('avatars'); }
The name of the file is automatically changed by Laravel
- To link the actual public folder of the project, with the folder Laravel stores the file, run:
php artisan storage:link. If this is done, the avatars directory will be available directly on the browser through a linx to the files. For Laravel to save on this directory, the parameter to the sore method must change to the public directory.$request->file('avatar')->store('public/avatars');

- To resize the image, the
Intervention/imagepackage can be used. To install it, run:composer require intervention/image. Then, to use this package, call make() on the Image class, and pass the file to be resized. The way the file is saved, now will be done with the Storage class, as now a rawImage is being manipulated.public function storeAvatar(Request $request) { $request->validate([ 'avatar' => 'required|image|max:3000', ]); $rawImg = Image::make($request->file('avatar'))->fit(256)->encode('jpg'); $user = auth()->user(); $filename = $user->id . '-' . uniqid() . '.jpg'; Storage::put('public/avatars/'.$filename, $rawImg); }
- Store the path of file on database. After saving the file, assign the the filename to the avatar property en the user, and call save() on the user.
$user->avatar = $filename; $user->save();
- in this way the first part of the path would need to be prepended to the path provided to html tags. Like this:
src="/storage/avatars/{{auth()->user()->avatar}}" - To avoid havind to do this, the path can be prepended on the model of user, this also allows to provide a fallback file, for the users where the avatar property is empty.
protected function avatar(): Attribute { return Attribute::make(get: function ($value) { return $value ? '/storage/avatars/' . $value : '/fallback-avatar.jpg'; }); }
- The value of the old avatar needs to be stored, so that it can be referenced later for deletion. This needs to be done before the new avatar overrides the previos avatar.
$oldAvatar =$user->avatar; - After the new avatar has been saved, the old avatar can be deleted.
if ($oldAvatar != "/fallback-avatar.jpg") { Storage::delete(str_replace("/storage", "public", $oldAvatar)); }
-
Create migration to add follows table.
php artisan make:migration create_follows_table -
The up() method would look like this:
public function up() { Schema::create('follows', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained(); //create column, then sets it as foreign key of the user table on thi id column. $table->unsignedBigInteger('followeduser'); $table->foreign('followeduser')->references('id')->on('users'); $table->timestamps(); }); }
-
Create a controller to handle the follow functionality.
php artisan make:controller FollowController -
Create a Follow model, to handle the database interaction.
php artisan make:model Follow -
On the controller create the methods for assigning and unassigning followers. When creatien the follow, enforcing rules may be desired, such as user can not follew self, and cant follow someone that one is already following.
- The unassignFolloger() method needs to be implemented.
public function unassignFollower(User $user) { Follow::where([['user_id', '=', auth()->user()->id], ['followeduser', '=', $user->id],])->delete(); return back()->with('success', 'Unfollowed user'); }
- As well as the logic to show a different button.
@if (!$isFollowing and auth()->user()->id != $user->id) <!-- code --> @endif
- The relationship between followers can be done in the User model class, the method would have to return the relationship.
public function followed() { return $this->hasMany(Follow::class, 'followeduser'); } public function following() { return $this->hasMany(Follow::class, 'user_id'); }
- The Follow model class, can return who followed who.
public function userFollowing() { return $this->belongsTo(User::class, 'user_id'); } public function userFollowed() { return $this->belongsTo(User::class, 'followeduser'); }
- On the user controller, this data would need to be passed down to the view, for instance:
public function profileFollowers(User $user) { $this->sharedProfileData($user); return view( 'profile-followers', ['followers' => $user->followed()->latest()->get(),] ); }
- On the view, on would just have to loop through the data.
@foreach ($followers as $follower) followers <a class="list-group-item list-group-item-action" href="/profile/{{ $follower->userFollowing->username}}"> <img class="avatar-tiny" src="{{$follower->userFollowing->avatar }}" /> {{$follower->userFollowing->username}} </a> @endforeach
- Creater the relationship between the user and the post, going through the follow table.
public function feedPosts() { //arguments on te hasManyThrough: //1 - Table on the other side of the relationship //2 - Table on the middle of the relationship //3 - Foreign key on the middle table //4 - Foreign key on the other side of the relationship //5 - Local key on the current table //6 - Local key on the middle table return $this->hasManyThrough(Post::class, Follow::class, 'user_id', 'user_id', 'id', 'followeduser'); }
- Send this data, to the view.
return view('homepage-feed', ['posts' => auth()->user()->feedPosts()->latest()->get()]);
- The pagination is done on the controller, by adding the
paginate()method to the query. This method takes the number of items per page as an argument.return view('homepage-feed', ['posts' => auth()->user()->feedPosts()->latest()->paginate(4)]);
- To include links to the next and previous pages, the view needs to include
{{ $posts->links() }}. - By default Laravel uses tailwind for css, to change this for the pagination, on
/app/Providers/AppServiceProvider.phpinside the methodboot()add:Paginator::useBootstrapFive();
- Where the title is set, add the following code:
<title> @isset($title) {{ $title }} | OurApp @else OurApp @endisset </title>
- Then on every page that need to have a custom title, set the variable title like this:
<x-layout :title="$post->title"> ... </x-layout> <!-- or --> <x-profile :sharedData="$sharedData" title="{{ $sharedData['user']->username }}'s Profile" > ... </x-profile>
![]() |
![]() |
|---|
This is a laravel feature, but requires composer to download. Run: composer require laravel/scout, then to allow laraver to use this, run: php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider".
-
The Post model class will need to declare it uses Searcheable, and declare de function "toSearchArray()", whic returns an array that describes on which fields the searh will work on.
use Searchable; //code.. public function toSearchArray() { return [ 'title' => $this->title, 'body' => $this->body ]; }
-
On the
.envfile, add the following:SCOUT_DRIVER=database -
On the BlogController, the search function needs to be implemented.
public function search($term) { return Post::search($term)->get(); }
-
Install nodejs, run:
sudo apt install nodejs -y -
Then to install the dependencies listed on package.json, run:
npm install, if nnpm is not installed, run:sudo apt install npm -y -
Copy all the contents from
public/main.csstoresources/css/app.css. The main.css file can now be deleted. -
On
resources/js/app.jsone can add the desired javascript code. -
To use the app.css and app.js files on the project, on the
layou.blade.phpfile, replace the css importation line with@vite(['resources/css/app.css'])and add@vite(['resources/js/app.js']) -
To see the changes, run
npm run dev. -
The command
npm run buildwill create a production version of the app on build subdirectory. -
After copyin the
live-search.jsfile to theresources/jsimport it on app.js. Run:npm install dompurifyto install the dependency. -
On the
app.jsfile add:import Search from './live-search'; //code... if (document.querySelector('.header-search-icon')) { new Search(); }
This will only instantiate a Search object if the search icon is present on the page.
-
Event Listeners go on the listen method inside the
app/Providers/EventServiceProvider.phpclass. For example this show code for an example event, and its listener:
Each of these should be o a different file, Laravel can help create these files, we just need to declare where on the EventServiceProvider.php file like this:

To generate these files usung Laravel run:
php artisan event:generate -
Now thes can be used anywhere from the aap. For example, for an event to fire when logging in, on the login method, after logging in,just add:
//... event(new OurExampleEvent()); //...
-
The listener can be used to do anything, for example, to send an email when the event is fired, on the handle method of the listener, add:
public function handle(OurExampleEvent $event) { Log::debug('OurExampleEvent was fired'); }
One of the default services for this on laravel is pusher. To use it create a free account
Then on the .env file change BROADCAST_DRIVER to pusher, and pusher configuration like bellow:
BROADCAST_DRIVER=pusher
---
PUSHER_APP_ID=123456
...
Then run: composer require pusher/pusher-php-server
-
Create a route:
Route::post('/send-message', [ChatController::class, "sendMessage"])->middleware('auth');
-
Create a controller, and on it a function to handle the request:
public function sendMessage(Request $request) { $fields = $request->validate([ 'message' => 'required', ]); $message = trim(strip_tags($fields['message'])); if (!$message) { return response()->noContent(); } broadcast(new ChatMessage(['user' => auth()->user(), 'message' => $message]))->toOthers(); return response()->noContent(); }
-
run:
npm install laravel-echo pusher-js -
uncomment
App\Providers\BroadcastServiceProvider::class, onconfig/app.php
- Create a MailTrap account, and on the
.envfile add the following (change username and password accordingly):
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=_your-username_
MAIL_PASSWORD=_your-password_
MAIL_ENCRYPTION=tls
- Create a template for the email, on
resources/views/create a filenew-post-email.blade.phpwith the following content:
<div style="font-family: sans-serif;">
<strong>{{$data['user']->username}}</strong>, {{$data['user']->username}} is a great post!.
</div>- Run:
php artisan make:mail NewPostEmail - Now we can send emails, for example, on the
PostControllerclass, on thestoremethod, add:
Mail::to(auth()->user()->email)->send(new NewPostEmail([
'user' => auth()->user(),
'title' => $newPost->title,
])
); - On the build method of the
NewPostEmailclass, add:
public function build() {
return $this->subject('Congratulations!')->view('new-post-email', ['data' => $this->data]);
}- Create a job by running:
php artisan make:job SendEmailJob - From a function, call dispatch, and a as parameter an intance or the job class, that was just created.
dispatch(new SendEmailJob([
'user' => auth()->user(),
'title' => $newPost->title,
]));- On the job class, make sure to receive the data on the constructor, and implement the body of the handle method:
//constructor
public $incoming;
public function __construct($incoming) {
$this->incoming = $incoming;
}
//handle method
public function handle() {
echo $this->incoming['user']->email;
Mail::to($this->incoming['user']->email)->send(new NewPostEmail([
'user' => $this->incoming['user'],
'title' => $this->incoming['title'],
]));
}-
To allow laravel to run the queue on the background, change the QUEUE_CONNECTION on the
.envfile todatabaseand run:php artisan queue:tableandphp artisan migrate -
To start the jobs on the queaue, run:
php artisan queue:work
-
Create a new template with te content to display.
-
run
php artisan make:mail RecapMail -
On the RecapMail class, add the following:
public function build(){ $post=Post::count(); $user=User::count(); return $this->subject('Site RECAP')->view('recapmail',['postCount'=>$post,'userCount'=>$user]); }
-
To actually schedule a task, on the
app/Console/Kernel.phpfile, inside the protected method schedule, add the following:$schedule->call(function(){ Mail::to('[email protected]')->send(new RecapMail()); })->everyMinute();
-
To make Laravel to start the scheduler, run:
php artisan schedule:work
- Use the cache class, for example, to cache the number of posts, on the homepage, add:
$postCount=Cache::remenber('postCount', 60, function(){ return Post::count(); }); return view('homepage',['postCount'=>$postCount]);
- On the
routes/api.phpfile,create routes for the login and create-post endpoints:Route::post('/login',[UserController::class,'loginAPI' ]); Route::post('/create-post',[BlogController::class,'storePostAPI' ])->middleware('auth:sanctum');
- On the
UserControllerclass, create a function to handle the login request:public function loginAPI(Request $request){ $incomingData = $request->validate([ 'username' => ['required',], 'password' => ['required',], ]); if (auth()->attempt($incomingData)) { $user = User::where('username', $incomingData['username']); $token = $user->createToken('authToken')->plainTextToken; } }
- On the
BlogControllerclass, create a function to handle the create-post endpoint.public function storePostAPI(Request $request) { $postFields = $request->validate([ 'title' => 'required', 'body' => 'required' ]); $postFields['title'] = strip_tags($postFields['title']); $postFields['body'] = strip_tags($postFields['body']); $postFields['user_id'] = auth()->id(); $newPost = Post::create($postFields); dispatch(new SendEmailJob([ 'user' => auth()->user(), 'title' => $newPost->title, ])); // return response(['message' => 'Post created successfully', 'post' => $newPost], 201); }
- Call the endpoint from a REST client.
1 ![]() |
2 ![]() |
|---|
-
Install docker desktop
- Obtain the repository and docker official gpg keys
sudo apt install -y ca-certificates curl gnupg lsb-release sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update -y- Download the docker-desktop and install.
sudo apt install -y ~/Downloads/docker-desktop-4.16.2-amd64.deb- To make sure it works run:
docker run -d -p 80:80 ubuntu:22.04. This will download the container.
-
From docker desktop, interact with the container, using the integrated terminal.
-
On the container run:
apt update. -
To be able to use Laravel on the container, some packages need to be installed.
apt install curl nano nginx php-cli unzip php8.1-fpm php-mysql php-mbstring php-xml php-bcmath php-curl php8.1-gd mysql-server -
Composer also needs to be installed, for this run:
curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php. Then execute the installer:php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
-
Since this in on a container, the server needs to be started manually, run:
/etc/init.d/nginx start, same for mysql, run:/etc/init.d/mysql startand php:/etc/init.d/php8.1-fpm start. -
Visiting localhost en the host machine, nginx is displayed, the on running on the container.

-
The mysql database needs to be configured, run:
mysqlto enter mysql tool, then run:ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'aoeaoe';to set the password for the root user. Then runexitto exit mysql tool.
-
Then, on the container run:
mysql_secure_installationto configure the mysql server. > Use the password configured on the previous step. Next time, to enter into the mysql tool, user and password need to be provided. For instance, run:mysql -u root -pthen type your password. -
Create a database and user for the laravel project. In the msql tool run:
CREATE DATABASE laravelapp; CREATE USER 'laraveluser'@'%' IDENTIFIED WITH mysql_native_password BY 'aoeuaoeu'; GRANT ALL ON laravelapp.* TO 'laraveluser'@'%';
On the host machine, run: docker cp /home/pop/Repositories/learning_laravel/laravel-projects/firstapp festive_lalande:/var/www/laravelapp
The
cpcommand takes to arguments, the source, and the destination, the destination will be: name_of_container:path_to_destination. The name of the container can be obtained by running:docker ps -a.
- On
/etc/nginx/sites-available/replace the contents of `default with the following:server { listen 80 default_server; listen [::]:80 default_server; root /var/www/laravelapp/public; add_header X-Frame-Options "SAMEORIGIN"; add_header X-XSS-Protection "1; mode=block"; add_header X-Content-Type-Options "nosniff"; client_max_body_size 10M; index index.html index.htm index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } error_page 404 /index.php; location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.(?!well-known).* { deny all; } }
- Restart nginx:
/etc/init.d/nginx restart - Change permissions of the storage forder:
chown -R www-data:www-data storage - Link the public directory with the storage directory by running:
php artisan storage:link - Modify the
.envfile, to point to the database configured on the container.
- Run the migrations:
php artisan migrate
It would serve the purpose of running commands every time the container is started.
- Create a file called
startup.shon the root of the project, with the following content:#!/bin/bash /etc/init.d/nginx start /etc/init.d/mysql start /etc/init.d/php8.1-fpm start /etc/init.d/cron start /etc/init.d/redis-server start - Allow this file to be executed:
chmod +x startup.sh - Now once the container starts, only
/startupneeds to be executed, it will take care of initializing all other services needed for laravel - To allow schedules and queues to run on the container, install cron.
apt install cron. To edit the jobs on cron, run:crontab -e. Add the following line towards the end of the file:* * * * * /usr/bin/php /var/www/laravelapp/artisan queue:work --max-time=59 * * * * * /usr/bin/php /var/www/laravelapp/artisan schedule:run - Now just start the cren service:
/etc/init.d/cron start
- Install redis:
apt install redis-server - On the root of the laravelapp project, run:
composer require predis/predis, to be able to use redis on the project. - Modify the
.envfile to contain these propertries and values:CACHE_DRIVER=redis QUEUE_CONNECTION=redis REDIS_CLIENT=predis - Start the redis server:
/etc/init.d/redis-server start
Create a VPS, with a provider like Digital Ocean or Linode.
Install nginx, and other needed packages:
sudo apt update
sudo apt install mysql-server mysql-client
sudo apt install nginx
sudo apt install php-cli unzip php8.1-fpm php-mysql php-mbstring php-xml php-bcmath php-curl php8.1-gd
Install composer:
curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php
php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
Access the server from the web browser

Alter root user of mysql, run: mysql then: ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'aoeuaoeu';
Then run: mysql_secure_installation and follow the instructions.
Launch the mysql tool: mysql -u root -p and create the database and user for the laravel app:
CREATE DATABASE laravelapp;
CREATE USER 'laraveluser'@'%' IDENTIFIED WITH mysql_native_password BY 'aoeuaoeu';
GRANT ALL ON laravelapp.* TO 'laraveluser'@'%';





























