First, what we're creating here?
Imagine a mobile app to park the car somewhere in a big city, to start/stop the parking.
Let's just quickly run through the features that our users would be able to do, those features will later become API endpoints. In a typical simple parking application, I imagine these features:
- User register
- User login
- User view/update profile/password
- Manage user's vehicles
- Get prices for parking zones/areas
- Start/stop parking at a chosen zone
- View the current/total price of parking
Notice: In this application, we will skip the actual payments for parking, because there are so many different ways to pay, depending on the country, so that could be a separate very long course, in the future.
When creating any application, I first start thinking about entities - it is just a more sophisticated word for database tables and Eloquent models. When I have a DB Schema in front of me, visually, then I can list the features of each entity.
So, what are we dealing with, in a parking application?
- Users
- Cars/vehicles
- Parking zones/areas with prices
- Parking events: start/stop
These are the main DB models, from them we can calculate everything and make any report we want.
Visually, the DB schema is this:
Yes, I draw it with pen and paper - even in the 21st century, it's by far the quickest way to let the unstructured thoughts out, visually.
Now, we can get to coding and create this schema with Laravel.
After creating our fresh Laravel project, we create these Models.
php artisan make:model Vehicle -m
The migration of the DB table:
Schema::create('vehicles', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained(); $table->string('plate_number'); $table->timestamps(); $table->softDeletes();});
Also, my personal preference is to immediately specify the $fillable
array in every Eloquent model, so I will do exactly that:
app/Models/Vehicle.php:
use Illuminate\Database\Eloquent\SoftDeletes; class Vehicle extends Model{ use HasFactory; use SoftDeletes; protected $fillable = ['user_id', 'plate_number'];}
Next, the parking zone/area entity. Let's just call it "zone" and add a few entries immediately.
php artisan make:model Zone -m
use App\Models\Zone; return new class extends Migration{ public function up() { Schema::create('zones', function (Blueprint $table) { $table->id(); $table->string('name'); $table->integer('price_per_hour'); $table->timestamps(); }); Zone::create(['name' => 'Green Zone', 'price_per_hour' => 100]); Zone::create(['name' => 'Yellow Zone', 'price_per_hour' => 200]); Zone::create(['name' => 'Red Zone', 'price_per_hour' => 300]); }};
Did you know that you can use the Eloquent model immediately here in the migrations? Another option is to generate a Seeder class and put the data there, but for the simplicity of this tutorial, I'm doing it right away in the migration.
Also, I agree that the field price_per_hour
is not very flexible: every city may have its logic for parking prices - per minute, hour, second, etc. So, in this case, just for clarity, I'm relying on the assumption that it's per hour, and the price will be in cents, as an integer.
And, of course, we need to make those two fields fillable:
app/Models/Zone.php:
class Zone extends Model{ use HasFactory; protected $fillable = ['name', 'price_per_hour'];}
Finally, when we start/stop parking, we will deal with the DB table "parkings".
php artisan make:model Parking -m
The fields in the table will be these:
Schema::create('parkings', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained(); $table->foreignId('vehicle_id')->constrained(); $table->foreignId('zone_id')->constrained(); $table->dateTime('start_time')->nullable(); $table->dateTime('stop_time')->nullable(); $table->integer('total_price')->nullable(); $table->timestamps();});
As you can see: three foreign key fields, start/stop time, and total_price at the end of the parking event.
And, of course, all fillable:
class Parking extends Model{ use HasFactory; protected $fillable = ['user_id', 'vehicle_id', 'zone_id', 'start_time', 'stop_time', 'total_price']; protected $casts = [ 'start_time' => 'datetime', 'stop_time' => 'datetime', ];}
Also, we add the $casts
to the datetime columns, to be able to easily convert them later to whichever format we want. You can read more about casts, in this article: Laravel Datetime to Carbon Automatically: $dates or $casts
Later, we will add relationship methods in the models, as we need them in the API endpoints.
But, for now, we're done with the DB structure.
After we run
php artisan migrate
We can generate a more presentable visual schema, with some SQL client tool, like DBeaver in my case:
i've just done coding a short laravel project using API based on a youtube video and was wondering why Povilas haven't done any API course in a while. decided to check here, lo and behold this article appears! thank you Povilas!
i agree with the pen and paper way of visualizing structures. i tried being fancy by using a tablet with a stylus to map things out and i figured in the end, nothing beats the quickness of basic pen and paper.
I was literally looking for another API course yesterday and found the older one you did but this is even better thanks :)
Great way of learning I hope to make a lot more please Povils from this kind tutorials, It's by far better than Videos
really wish you would have started from the initial laravel setup. can I do this using Laravel Jetstream and have it as part of my project If so How ? what would I have to change to get it to work using the jetstream auth ?
Thanks for all the hard work you do for us.
Sorry, that's not something I can answer in a comment here, I would need to re-build all the course. My "initial laravel setup" as you called it does not include Jetstream.
greate part, but stiil don't understand why parking has user_id? I think we can accees to user via vehiculs. Why so we need users without Veheculs! thank you
It's faster to query by user_id without relationship, to load the relationship each time is a performance issue.
great technical responce ! thank you , but in this case, user can acces to parking without having vehicule.. we need to fix it using this query : where user_id in parking and user_id in vehicules ?
Hmm, good point, probably you're right.
First i have the same idea as you fatima, but then i think user_id in parking can be usefull if the user parkiing not the owner of the vihicule like a brother or son ... .
Hi there!
I got a question regarding using
datetime
mysql datatype instead oftimestamp
. By default, the$table->timestamps()
method will create timestamp fields. So, in the same table we will have two datatypes for dates.For the
start_time
andstop_time
in the Parking model, it was chosen to usedatetime
field. Is there any specific reason behind that? Would it be more suitable than using timestamp?Thanks!
To be honest, I don't remember the reason why it was done this way in this course, but the difference is not significant in many cases. It might be important for your specific project, read more in this Stackoverflow thread.
Also, a great explanation by Michael Dyrynda on Laracasts forum: "The difference between the two is that timestamp can use (MySQL's) CURRENT_TIMESTAMP as its value, whenever the database record is updated. This can be handled at the database level and is great for Laravel's created_at and updated_at fields."
Thanks so much for the prompt reply, Povilas, and for providing such great content. I've been watching your videos on youtube and today I became a member. Cheers
Does anyone knows the impact of converting existing timestamp field to datetime field having data inside that field.
I don't think there's an impact, at least I haven't seen anyone reporting it anywhere.