Ralph J. Smit Laravel Software Engineer
Laravel provides a whole bunch of tools to interact with your application's database. The most important one is Eloquent, which is one of Laravel's most popular and influential features. In a world of powerful, but complex tools, Eloquent stands out for its simplicity. But before we dive into Eloquent, it's a good idea to first get everything set up and understanding the basics of Laravel's database functionality: connections & migrations.
In this tutorial, I'll first show you how to configure your database credentials and connections. Then, I'll explain how Laravel migrations work and give you a few examples. After that, I'll explain how to interact with your database and retrieve data.
In a next tutorial I'll show you how to get started with Eloquent and how to use seeders & factories to turn you into a power-user💪
Laravel database connections & configuration
The Laravel configuration for database access lives in your config/database.php and .env files. Similar to other parts of Laravel, here you can define multiple connections and then decide which connection the code will use by default.
By default, there's one connection for each of the database drivers. A database driver is how Laravel retrieves the data. So, there's a database driver for mysql, sqlite, pgsql, redis and more.
If you open up the config/database.php file, you'll see the following default connections.
'default' => env('DB_CONNECTION', 'mysql'), 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'url' => env('DATABASE_URL'), 'database' => env('DB_DATABASE', database_path('database.sqlite')), 'prefix' => '', 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), ], 'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], 'pgsql' => [ 'driver' => 'pgsql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '5432'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'prefix_indexes' => true, 'schema' => 'public', 'sslmode' => 'prefer', ], 'sqlsrv' => [ 'driver' => 'sqlsrv', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', '1433'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'prefix_indexes' => true, ], ],
Nothing stops you from deleting or modifying them. And neither can you only use one connection per driver: you could have 4 connections, all with the mysql
driver if you wanted.
In many apps, you'll just pick the one connection you need, set it to 'default' => 'driver_I_need'
and store the necessary connection details in .env.
I usually keep all the connections, even if I don't use them. It could be that I need them later and then it'll come in handy.
This 'connection-driven' design is very handy if you need to work with multiple databases. E.g. one database for reading and one database for writing. Support for multiple connections makes this possible.
Laravel changing database credentials
You database credentials are usually secret. That's why you don't want to commit them to version control (that means putting it in a file that is stored in Git). The .env file is the perfect place for such 'sensitive' information.
In the config/database.php, you'll see lines like this:
env('DB_HOST', '127.0.0.1') // Get the value of 'DB_HOST' from .env and if it doesn't exist, use '127.0.0.1'.
So if you take a look at your .env (or .env.example, the template file for .env), you'll use it like this:
DB_CONNECTION=mysqlDB_HOST=127.0.0.1DB_PORT=3306DB_DATABASE=my_database_nameDB_USERNAME=rootDB_PASSWORD=
How to get my database credentials?
That depends on whether you're using you app on a server or on your computer. If you're using Laravel on a server (in most cases the production
environment), you'll need to create a database there and use those credentials.
If you're running Laravel locally, e.g. on your computer, you'll need credentials for a database that is hosted by your computer. If you want to get up and running quickly with a database on macOS, I've written an article on setting up local databases with DBngin and Laravel. If you're on Windows or Linux, you'll need to search for a tool that does the same for you.
Using a different database connection
By default, any database query will use the default connection. Anytime you need a specific connection, you can use it as follows:
$users = DB::connection('second')->select('select * from users');
Laravel config/database.php additional settings
If you look further at the config/database.php file, you'll see that there are also connection options for the migrations table name and for Redis.
Usually you don't need to change the name for the migrations table, as migrations
is totally fine.
For Redis, you'll only need those settings if you decide to use Queues and Jobs: a more advanced feature of Laravel. For now, you can totally skip this and come back to it when you ever want to implement queues.
Laravel database migrations
Now that we've correctly set up and connected our database, the next thing we'll look at is Laravel database migrations. So first, what are migrations?
Definition of a database migration
A database migration is how you define changes to your database's structure. For each table you add and for each column you modify, you create a new database migration. This allows you to go from empty database to correctly configured database with only one command.
Tables vs columns: how is a database structured?
A database consists of multiple tables. So e.g. you have a table for your users and a table for your password resets. Each table consists of columns and rows. Just like a regular table.
Default Laravel users table migration
So, let's dive in. How does an actual migration look like? As an example, take the default migration Laravel offers for the users table.
Go to the folder database/migrations. Here you'll see list of all the migrations. Every file is a single migration. The file name contains a timestamp, so that Laravel can determine the order of the migrations.
<?php use Illuminate\Database\Migrations\Migration;use Illuminate\Database\Schema\Blueprint;use Illuminate\Support\Facades\Schema; class CreateUsersTable extends Migration{ /** * Run the migrations. * * @return void */ public function up() { // Create a table called 'users' Schema::create('users', function (Blueprint $table) { $table->id(); // Each user has an id $table->string('name'); // Each user has a name $table->string('email')->unique(); // Each user has a unique email $table->timestamp('email_verified_at')->nullable(); // Only applicable if you need email verification $table->string('password'); // Each user has a password $table->rememberToken(); // Each user has a remember token (to allow him or her to stay signed in $table->timestamps(); // Each user has two timestamps: created_at and last_edited_time }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); // To undo this migration, we need to drop the table 'users' }}
Each migration is built up in the same way. It consists of two methods: up()
and down()
.
-
Migration
up()
: this function is run when you migrate your database. -
Migration
down()
: this function is the opposite function of theup()
method. It is run when you rollback your database (e.g. you have 5 migrations, you're not satisfied with the last one, so you rollback the latest migration and bring your database back to that state).
Usually you initially only bother yourself with the up()
call. You only look at the down()
method after you've defined what the up()
does. In short, the down()
method undoes the changes made by the up()
method.
Creating database migrations
So, how do we create database migrations? As always, there's an artisan command for that.
php artisan make:migration create_posts_table
This command creates a file in the database/migrations folder with {timestamp}_create_posts_table. If Laravel can automatically determine the table name from the migration name, it'll pre-fill that name. In this case the table name would be posts
.
But you can also specify the table name yourself. If you want to create a new table, run:
php artisan make:migration create_tablename_table --create=tablename
And if you want to update a table, run:
php artisan make:migration update_tablename_table --table=tablename
Migration calls on the `Schema::` facade
The whole point of migrations is to say: 'I want this and this column created/modified/deleted in this and this way'. However, you can't just plainly type some code and expect it to work.
In this phase, you need to distinct between two things:
-
Creating, modifying and deleting tables
-
Creating, modifying and deleting columns
An interesting thing is that each of the methods contain a call to the Schema::
facade. For each change to a database table, you use a new Schema::
call. For modifying columns inside that table, you add code in the Schema::
call itself. If this sounds very confusing, don't worry, it's actually very simple.
Creating, modifying or deleting tables
The Schema::
facade itself is not so interesting. In fact, you can see it as a wrapper for the actual code that contains some configuration, like whether you're creating a new table or only updating it.
The Schema::method()
is used for adding, updating or deleting tables. Each Schema::method()
contains the code to add, update or delete columns.
Here are a few important use cases of the Schema::method()
facade:
-
If you want to create a table, use
Schema::create('tablename', function(Blueprint $table) { // Create columns here } );
-
If you want to update a table, use
Schema::table('tablename', function(Blueprint $table) { `// Create columns here`
} );
-
Check whether a table exists:
Schema::hasTable('users')
-
Check whether a column exists:
Schema::hasColumn('users')
-
Migrate a different connection than your default:
Schema::connection('sqlite')->create('users', ..
And then there are also a few methods for dropping things:
-
Rename a table:
Schema::rename('users', 'my_users');
-
Drop a table:
Schema::drop('users');
-
Drop a table if it exists:
Schema::dropIfExists('users');
Creating columns
Laravel offers many methods to create, modify or drop columns. The documentation specifies them all, but here's a list of the most important ones:
IDs and timestamps
Let's start with two simple functions. These functions are included by default in every migration. They create a column called id
and two columns with timestamps: **created_at**
and **updated_at**
. It is advisable to use this on every table you create.
$table->id();$table->timestamps();
Creating a string column
$table->string('column_name');
Creating a date column, a datetime column and a timestamp column
Storing dates and times is somewhat tricky, but understanding the differences between the date()
, datetime()
, timestamp()
and time()
helpers can be very useful.
The $table->date()
helper creates a column that only contains the date.
The $table->datetime()
helper creates a column that contains both the date and the time part.
The $table->timestamp()
helper creates a column that contains both the date and the time, but supports timezones. Effectively, when you store a timestamp that is in a different timezone than UTC, your database will convert the timezone to UTC. When you get the timestamp again, the database will convert the time back to the correct timezone.
The $table->time()
helper creates a column that contains only the time part.
See the examples for storing dates and times in Laravel below:
$table->date('column_name'); // YYYY-MM-DD$table->datetime('column_name'); // YYYY-MM-DD HH:MM:SS$table->timestamp('column_name'); // YYYY-MM-DD HH:MM:SS in UTC$table->time('column_name'); // HH:MM:SS
Creating an integer column
$table->integer('column_name');
Creating a decimal number column
$table->decimal('column_name', 8, 2); // This creates a decimal column with a precision of 8 and 2 digits.// This means that is can contain numbers up to 999999.99 (6 numbers before and 2 numbers after). // Increase precision for larger numbers.
Creating a true/false column (boolean)
$table->boolean('column_name');
Creating a json column
$table->json('column_name'); // Useful for storing arrays. Convert an array to json() when you store it and convert it back to array when you read it. // Eloquent can do this automatically for you and I'll write an article about this soon.
Creating a foreignId column
$table->foreignId('user_id'); /*This creates a column that can contains an id. This shortcut will create a column that references the column 'id' on the table 'users'. Very useful with Eloquent Relationships. Checkout the following example, where a user has many posts: Create a table 'users' and a table 'posts'. The table 'posts' contains:$table->foreignId('user_id'); In your Eloquent User model, add the following:*/ public function posts(): HasMany { return $this->hasMany(Post::class);} // In your Eloquent Post model, add the following:public function user(): BelongsTo { return $this->belongsTo(User::class);} // Now use it like this:$user = auth()->user(); $post = $user->posts()->create([ 'title' => 'Title', // Or any other column you create //]); // And get the user when you have a post:$user_retrieved_from_post = $post->user;
Creating a column with a long text
$table->longText('column_name'); // This creates a column that can contain very lots of text. Take our example of a User that has many Posts again. You wouldn't use longText() for the title, but it would be ideal for the body text.
Creating the columns for softdeletes
This creates a column deleted_at
with a timestamp. This is used for softdeletes. When you delete a row that uses soft deletes, the record will not actually be deleted, but it will set the deleted_at
column to now()
.
Laravel will now know that this row has been deleted earlier and should not be included in your query results (except when you specifically ask for them). This is useful for restoring data, but be aware that it is prone to human errors and will also clutter your database.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\SoftDeletes; // <= Add this class Post extends Model{ use SoftDeletes; <= Add this}
In your migration: Schema::table('posts', function (Blueprint $table) { $table->softDeletes();}); Schema::table('posts', function (Blueprint $table) { $table->dropSoftDeletes();});
If you want to see more column types, please refer to the documentation.
Dropping a column
If you want to delete a column, that is done inside a Schema::table()
call:
Schema::table('users', function (Blueprint $table) { $table->dropColumn('timezone');});
How to get data from the database
So, how do we now get data into the database and how do we retrieve it?
Laravel offers two tools for that: the DB::
facade aka the Query Builder, and Eloquent. First, we'll dive into the DB::
facade and in a next tutorial I'll show you how to get started with Eloquent. The Query Builder is similar to Eloquent, so it'll help you if you already have a basic idea of how it works.
Getting all the rows with the Laravel Query Builder
To get the results, you start by making a call to the DB::
facade with a method called table('table_name')
. After that initial part, you can start 'chaining' database calls. Chaining means that calls look like this: method()->otherMethod()->andAnother($coolParameter)->get();
.
Keep in mind that ->get()
or ->first()
will always be the last call you make, it tells Laravel: 'go, now do what I specified before'. You see?
use Illuminate\Support\Facades\DB; $users = DB::table('users')->get();
Keep this in mind: this database call will not return a simple array, but a so-called collection. You can view collections like a sort of 'super-arrays'. To get info from a single instance, you can get it as if the value is a property of a class, e.g. $user->email
or $post->title
. Here, email
and title
are both columns.
foreach ($users as $user) { echo $user->name;}
Getting a single row with the Query Builder
To get a single row, you can use ->first()
instead of ->get()
:
use Illuminate\Support\Facades\DB; $users = DB::table('users')->first();
And you can add conditions:
use Illuminate\Support\Facades\DB; // Gets the first item where 'email' = 'email@example.com'$user = DB::table('users')->where('email', 'email@example.com')->first(); // Gets the first item where 'email' = 'email@example.com' AND 'job_title' = 'manager'$manager = DB::table('users')->where('email', 'email@example.com')->where('job_title', 'manager')->first(); // Gets the first item where 'email' = 'email@example.com' OR 'email' = 'secondary@example.com'$user = DB::table('users')->where('email', 'email@example.com')->orWhere('email', 'secondary@example.com')->first();
Working with thousands of results: chunking
Another interesting thing is chunking database queries. This comes in handy when you have large numbers of records, in the thousands or millions. If you use ->get()
to get them all, you're likely to hit your memory limits – and the app will crash.
In order to solve that, use ``Model::chunk()`:
use Illuminate\Support\Facades\DB; DB::table('users')->orderBy('id')->chunk(100, function ($users) { foreach ($users as $user) { // }});
This function returns a maximum of 100 records at a time and iterates over them with a foreach
loop.
The chunk()
function takes a closure (a PHP function) as a parameter. That means that you don't have access to other variables defined before. Solve that by adding the use ($read_only_variable)
or use (&$read_and_write_variable)
after the closure definition, like so:
use Illuminate\Support\Facades\DB; // Only read the variable in the closure (recommended)DB::table('users')->orderBy('id')->chunk(100, function ($users) use ($read_only_variable) { foreach ($users as $user) { // }}); // Modify the variable in the closureDB::table('users')->orderBy('id')->chunk(100, function ($users) use (&$read_and_write_variable) { foreach ($users as $user) { // }});
How to determine if a Laravel database record exists
This example is straight from the documentation, but it is very self-explanatory. It checks whether a record exists:
if (DB::table('orders')->where('finalized', 1)->exists()) { // ...} if (DB::table('orders')->where('finalized', 1)->doesntExist()) { // ...}
Special database functions
In addition to the above function, Laravel also offers some ways to count certain values or calculate the average. It also supports getting the lowest or highest value in the database with the min
and max
method.
You can also chain ->where()
calls for more exact results, see the third example.
use Illuminate\Support\Facades\DB; // Get the number of users$users = DB::table('users')->count(); // Get the user with the lowest id and the order with the highest price$user = DB::table('users')->min('id');$price = DB::table('orders')->max('price'); // Determine the average order price of orders that are finalized$price = DB::table('orders') ->where('finalized', 1) ->avg('price');
Updating records in the database
Now that we've seen how to connect to the database and get records from it, let's also take a look at how to insert, update and delete records.
Add records to the database
The easiest way to insert a record is with the insert method:
use Illuminate\Support\Facades\DB; // Insert one recordDB::table('users')->insert([ 'email' => 'kayla@example.com', 'firstname' => 'Kayla', 'lastname' => 'Murray',]);
Insert multiple records by passing an array with arrays:
// Insert multiple recordsDB::table('users')->insert([ ['email' => 'terrence@example.com', 'firstname' => 'Terrence', 'lastname' => 'Robertson'], ['email' => 'Jake@example.com', 'firstname' => 'Jake', 'lastname' => 'McDonald'],]);
Update or insert a record in the database
Take a look at the below code. This will update the price
column if there is a record with matching departure
and destination
columns already. If there is no record that has those departure
and destination
values, it will create it.
DB::table('flights')->upsert([ ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99], ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]], ['departure', 'destination'], ['price']); // Or use the following syntax, which is usually used for updating or creating single records. // The updateOrInsert() method takes two arrays as parameters: the first array contains// the conditions to find a record, and the second array contains the columns to update. DB::table('users')->updateOrInsert( ['departure' => 'Oakland', 'destination' => 'San Diego'], ['price' => '99', ]);
Updating a record in the database
To update one or more records, use the below code. This will update the column votes
on the user
with an id
of 1
. In this example, if you leave out the line with ->where()
, it will update all the users.
$updated_user = DB::table('users') ->where('id', 1) ->update(['votes' => 1]);
Deleting one or more records
To delete one or multiple records, use the ->delete()
method. This can be used for both deleting multiple records or a single record.
// Delete users with more than 100 votesDB::table('users')->where('votes', '>', 100)->delete();
There are a few other methods to explore, but those discussed above are the most important methods.
The other methods include things like ->insertGetId()
(for auto-incrementing ids), ->increment()
and->decrement()
for integers and using update on JSON columns. Checkout the docs for those.
Conclusion
As you've seen, getting and retrieving data from the database is not hard. It inly requires a little configuration to get started, and after you've got used to it, it's very easy. The Query Builder is a powerful feature of Laravel, but don't forget that there's an even better way: Laravel Eloquent. In the next article, I'll discuss how Eloquent works and show you how to harness the power of Laravel Eloquent.
As always, if you have any questions, comments, updates or additions, please leave a comment below and I'll change the article and credit you. ⚡️
Published by Ralph J. Smit on in Laravel . Last updated on 11 March 2022 .