Skip to main content
Summary

배열 데이터에서 데이터베이스로 넘어가기

앞선 라우팅과 컨트롤러 예제에서는 게시글 데이터를 컨트롤러 안의 배열로 준비했습니다. 작은 예제를 설명하기에는 충분했지만, 실제 애플리케이션에서는 곧 한계가 드러납니다.

배열 데이터는 코드를 수정해야만 바뀝니다. 서버를 다시 배포하지 않으면 새 게시글을 추가할 수 없고, 사용자가 작성한 글을 저장할 수도 없습니다. 검색, 정렬, 페이지네이션, 작성자 연결 같은 기능도 점점 불편해집니다.

그래서 웹 애플리케이션은 대부분 데이터베이스를 사용합니다. 데이터베이스는 애플리케이션이 다루는 정보를 오래 저장하고, 필요한 조건으로 다시 꺼내기 위한 전용 저장소입니다.

Laravel에서 데이터베이스를 배운다는 것은 단순히 SQL을 작성하는 법만 익힌다는 뜻이 아닙니다. 더 중요한 것은 데이터 구조를 코드로 관리하는 방법을 익히는 것입니다. 그 역할을 하는 도구가 바로 마이그레이션입니다.

마이그레이션을 사용하면 게시글 테이블에 어떤 컬럼이 있는지, 어떤 인덱스가 필요한지, 다른 테이블과 어떤 관계를 맺는지를 PHP 코드로 기록할 수 있습니다. 이 파일을 Git에 올리면 팀원도 같은 구조의 데이터베이스를 만들 수 있고, 배포 서버에서도 동일한 구조를 재현할 수 있습니다.

데이터베이스와 마이그레이션AI 생성 이미지

어떤 데이터베이스를 선택할까

Laravel은 여러 데이터베이스를 지원합니다. 학습 단계에서 자주 만나는 선택지는 SQLite, MySQL 또는 MariaDB, PostgreSQL입니다.

SQLite는 하나의 파일에 데이터를 저장하는 가벼운 데이터베이스입니다. 별도 서버를 설치하지 않아도 되므로 Laravel을 처음 배울 때 좋습니다. 보통 database/database.sqlite 같은 파일을 만들고 .env에서 연결하면 바로 사용할 수 있습니다. 테스트용 데이터베이스로도 자주 사용됩니다.

MySQL과 MariaDB는 웹 서비스에서 널리 쓰이는 데이터베이스입니다. 자료가 많고 호스팅 환경에서 지원하는 경우가 많습니다. 블로그, 쇼핑몰, 사내 관리 도구처럼 일반적인 웹 애플리케이션을 만들 때 무난한 선택입니다.

PostgreSQL은 기능이 풍부하고 데이터 무결성에 강한 데이터베이스입니다. 복잡한 질의, JSON 컬럼, 위치 정보, 엄격한 타입 관리가 필요한 서비스에서 많이 사용됩니다.

초보자에게 중요한 기준은 "무엇이 가장 강력한가"보다 "지금 환경에서 안정적으로 실행할 수 있는가"입니다. 처음에는 SQLite로 흐름을 익히고, 팀 프로젝트나 배포 환경에 맞춰 MySQL 또는 PostgreSQL로 넘어가도 괜찮습니다. Laravel의 마이그레이션을 사용하면 데이터베이스 종류가 바뀌어도 테이블 구조를 다시 만드는 흐름은 크게 달라지지 않습니다.

SQLite를 사용할 경우 먼저 데이터베이스 파일을 만듭니다.

touch database/database.sqlite

Windows PowerShell에서는 다음처럼 만들 수 있습니다.

New-Item -ItemType File database/database.sqlite

그리고 .env는 예를 들어 다음처럼 설정합니다.

DB_CONNECTION=sqlite
DB_DATABASE=/absolute/path/to/board_app/database/database.sqlite

DB_DATABASE에는 현재 프로젝트 안에 있는 database/database.sqlite 파일의 실제 경로를 적습니다. Laravel 설치 과정에서 SQLite를 선택했다면 이 파일이 이미 만들어져 있을 수 있고, 프로젝트 기본 설정에 따라 DB_DATABASE를 생략해도 database/database.sqlite를 바라보는 경우가 있습니다. 다만 처음 배울 때는 경로를 명시해 두면 연결 문제를 줄일 수 있습니다.

MySQL을 사용한다면 .env는 보통 다음과 비슷합니다.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=board_app
DB_USERNAME=root
DB_PASSWORD=

데이터베이스 연결 정보는 코드가 아니라 환경 설정입니다. 그래서 .env에 적습니다. 같은 Laravel 코드라도 내 컴퓨터에서는 SQLite를 쓰고, 운영 서버에서는 MySQL을 쓰는 식으로 나눌 수 있습니다.

.env를 바꿨는데도 Laravel이 예전 데이터베이스로 연결되는 것처럼 보인다면 설정 캐시가 남아 있을 수 있습니다. 그럴 때는 다음 명령으로 캐시를 비운 뒤 다시 시도합니다.

php artisan config:clear

마이그레이션은 테이블 설계도다

마이그레이션 파일은 database/migrations 디렉터리에 저장됩니다. Laravel 프로젝트를 만들면 이미 사용자, 비밀번호 재설정, 작업 큐 등에 필요한 기본 마이그레이션이 들어 있을 수 있습니다.

이제 게시글을 저장할 posts 테이블을 만들어 보겠습니다.

php artisan make:migration create_posts_table

명령을 실행하면 database/migrations 아래에 날짜와 시간이 붙은 PHP 파일이 생성됩니다. 파일 이름에 시간이 들어가는 이유는 마이그레이션 실행 순서를 정하기 위해서입니다.

생성된 파일을 열어 다음처럼 작성합니다.

use IlluminateDatabaseMigrationsMigration;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateSupportFacadesSchema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
            $table->string('title');
            $table->text('body');
            $table->boolean('is_published')->default(false);
            $table->timestamp('published_at')->nullable();
            $table->timestamps();

            $table->index(['is_published', 'published_at']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

마이그레이션에는 보통 up()과 down() 메서드가 있습니다.

up()은 앞으로 적용할 변경입니다. 여기서는 posts 테이블을 만듭니다. down()은 되돌릴 때 실행할 변경입니다. 여기서는 posts 테이블을 삭제합니다.

컬럼을 하나씩 살펴보겠습니다.

$table->id()는 기본 키를 만듭니다. 기본 키는 각 행을 구분하는 고유한 값입니다. 게시글 상세 URL에서 /posts/3처럼 특정 게시글을 찾을 때 이 값이 사용됩니다.

$table->foreignId('user_id')는 작성자를 연결하기 위한 컬럼입니다. 아직 인증 기능을 만들지는 않았지만, 나중에 게시글과 사용자를 연결하려면 이 컬럼이 필요합니다. constrained()는 관례에 따라 users 테이블의 id를 참조하도록 외래 키를 만듭니다. 외래 키는 잘못된 관계가 저장되지 않도록 데이터베이스가 검사하는 규칙입니다.

이 예제는 users 테이블이 이미 있거나, Laravel의 기본 사용자 마이그레이션을 함께 사용할 것을 전제로 합니다. 아직 사용자 테이블을 전혀 만들지 않을 생각이라면 처음 학습 단계에서는 user_id 줄을 잠시 빼고 진행해도 됩니다.

nullable()은 값이 비어 있어도 된다는 뜻입니다. 지금 단계에서는 로그인한 작성자가 없을 수 있으므로 임시로 허용합니다. nullOnDelete()는 연결된 사용자가 삭제되었을 때 게시글의 user_id를 null로 바꿉니다. 실무에서는 서비스 정책에 따라 게시글도 함께 삭제할지, 작성자 정보만 비울지 결정해야 합니다.

$table->string('title')은 짧은 문자열 컬럼입니다. 제목처럼 길이가 제한된 값에 사용합니다. $table->text('body')는 긴 본문을 저장할 때 사용합니다.

is_published와 published_at은 게시글 공개 여부를 다루기 위한 컬럼입니다. 지금 당장 화면에서 쓰지 않더라도, 게시글 목록에서 "공개된 글만 보여 주기" 같은 기능을 만들 때 필요합니다.

$table->timestamps()는 created_at과 updated_at 컬럼을 만듭니다. 데이터가 언제 생성되고 수정되었는지 기록하는 컬럼입니다. Laravel의 Eloquent는 이 두 컬럼을 기본적으로 사용합니다. 이어지는 Eloquent ORM 학습에서 이 연결을 더 자세히 다룹니다.

마지막의 index()는 인덱스를 만듭니다. 인덱스는 데이터베이스가 특정 조건으로 데이터를 더 빨리 찾도록 돕는 구조입니다. 예를 들어 공개된 게시글을 발행일 기준으로 조회하는 기능이 많다면 is_published, published_at 조합에 인덱스를 둘 수 있습니다.

인덱스는 많을수록 무조건 좋은 것이 아닙니다. 조회는 빨라질 수 있지만 저장과 수정에는 부담이 생깁니다. 처음에는 자주 검색하거나 정렬하는 컬럼에만 신중하게 추가하면 됩니다.

마이그레이션 실행하기

마이그레이션 파일은 작성만 해서는 데이터베이스에 반영되지 않습니다. 실제로 실행해야 합니다.

php artisan migrate

이 명령은 아직 실행되지 않은 마이그레이션을 찾아 순서대로 적용합니다. 성공하면 데이터베이스에 posts 테이블이 만들어집니다.

현재 마이그레이션 상태를 확인하고 싶다면 다음 명령을 사용할 수 있습니다.

php artisan migrate:status

개발 중에는 테이블 구조를 잘못 만들 때가 많습니다. 이때 명령을 상황에 맞게 골라야 합니다.

php artisan migrate:rollback

rollback은 마지막으로 실행한 마이그레이션 묶음을 되돌립니다. 방금 만든 테이블을 취소하고 마이그레이션 파일을 수정한 뒤 다시 migrate할 때 유용합니다.

php artisan migrate:fresh

fresh는 모든 테이블을 삭제한 뒤 마이그레이션을 처음부터 다시 실행합니다. 개발 초기에는 편리하지만, 기존 데이터가 모두 사라집니다. 운영 서버에서는 매우 위험한 명령입니다.

php artisan migrate:fresh --seed

이 명령은 테이블을 새로 만들고 이어서 시더를 실행합니다. 화면 개발을 위해 테스트 데이터가 반복해서 필요할 때 자주 사용합니다.

명령을 정리하면 다음과 같습니다.

명령사용 상황주의할 점
php artisan migrate새 마이그레이션을 적용할 때기본 실행 명령
php artisan migrate:rollback방금 적용한 변경을 되돌릴 때되돌릴 범위를 확인해야 함
php artisan migrate:fresh개발 DB를 처음부터 다시 만들 때모든 데이터 삭제
php artisan migrate:fresh --seedDB 초기화 후 테스트 데이터까지 넣을 때운영 환경에서 사용 금지
php artisan db:seed시더만 다시 실행할 때중복 데이터 생성 여부 확인

중요한 기준은 하나입니다. 데이터를 지워도 되는 환경인지 먼저 확인해야 합니다. 로컬 개발 데이터베이스라면 fresh가 편리합니다. 팀원이 함께 쓰는 개발 서버나 운영 서버라면 신중해야 합니다.

기존 테이블을 바꾸는 마이그레이션

테이블은 처음 설계한 대로 끝나지 않습니다. 기능이 늘어나면 컬럼을 추가하거나 인덱스를 바꿔야 합니다. 이때 기존 마이그레이션 파일을 수정해서 다시 실행하는 방식은 조심해야 합니다.

아직 혼자 로컬에서만 작업하고 아무도 해당 마이그레이션을 실행하지 않았다면 파일을 고쳐도 큰 문제가 없습니다. 하지만 이미 팀원이나 서버에서 실행된 마이그레이션이라면 새 마이그레이션을 추가하는 편이 안전합니다.

예를 들어 게시글 요약 컬럼을 추가해 보겠습니다.

php artisan make:migration add_excerpt_to_posts_table
use IlluminateDatabaseMigrationsMigration;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateSupportFacadesSchema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->string('excerpt')->nullable();
        });
    }

    public function down(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropColumn('excerpt');
        });
    }
};

Schema::create()는 새 테이블을 만들 때 사용하고, Schema::table()은 기존 테이블을 변경할 때 사용합니다.

컬럼 위치를 맞추기 위해 after('title') 같은 옵션을 쓰는 예제도 자주 볼 수 있습니다. 다만 이런 위치 지정 옵션은 데이터베이스 종류에 따라 지원 방식이 다를 수 있습니다. 이 책에서는 SQLite, MySQL/MariaDB, PostgreSQL을 오가며 학습할 수 있도록 꼭 필요하지 않은 위치 지정은 생략하겠습니다.

이런 방식으로 변경 이력을 남기면 데이터베이스 구조가 시간 순서대로 기록됩니다. 나중에 문제가 생겼을 때 "언제 어떤 컬럼이 추가되었는지" 추적할 수 있습니다.

테스트 데이터를 반복해서 만들기

테이블만 있다고 화면을 만들 수 있는 것은 아닙니다. 게시글 목록 화면을 확인하려면 실제 데이터가 필요합니다. 매번 데이터베이스 도구를 열어 직접 입력하면 번거롭고 실수도 많아집니다.

Laravel에서는 시더와 팩토리를 사용해 테스트 데이터를 코드로 만들 수 있습니다.

시더는 "어떤 데이터를 넣을지" 실행하는 클래스입니다. 팩토리는 "데이터 한 건의 모양을 어떻게 만들지" 정의하는 클래스입니다.

먼저 게시글 모델과 팩토리를 준비합니다. 모델은 이어지는 Eloquent ORM 학습에서 자세히 배우지만, 지금은 posts 테이블을 PHP 코드에서 다루기 위한 클래스라고 이해하면 충분합니다.

php artisan make:model Post -f

이 명령은 app/Models/Post.php 모델과 database/factories/PostFactory.php 팩토리를 만듭니다. 이미 모델이 있다면 팩토리만 따로 만들 수도 있습니다.

php artisan make:factory PostFactory --model=Post

팩토리 파일을 다음처럼 작성합니다.

namespace DatabaseFactories;

use IlluminateDatabaseEloquentFactoriesFactory;

class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'title' => fake()->sentence(4),
            'body' => fake()->paragraphs(3, true),
            'is_published' => true,
            'published_at' => now()->subDays(fake()->numberBetween(0, 30)),
        ];
    }
}

fake()는 임의의 문장, 이름, 날짜 같은 값을 만들어 줍니다. 이렇게 하면 매번 비슷하지만 조금씩 다른 게시글 데이터를 만들 수 있습니다.

이제 시더를 만듭니다.

php artisan make:seeder PostSeeder

database/seeders/PostSeeder.php를 열어 다음처럼 작성합니다.

namespace DatabaseSeeders;

use AppModelsPost;
use IlluminateDatabaseSeeder;

class PostSeeder extends Seeder
{
    public function run(): void
    {
        Post::factory()->count(20)->create();
    }
}

그리고 database/seeders/DatabaseSeeder.php에서 이 시더를 호출합니다.

namespace DatabaseSeeders;

use IlluminateDatabaseSeeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            PostSeeder::class,
        ]);
    }
}

이제 다음 명령으로 테스트 데이터를 넣을 수 있습니다.

php artisan db:seed

데이터베이스를 처음부터 다시 만들고 테스트 데이터까지 넣고 싶다면 다음 명령을 사용합니다.

php artisan migrate:fresh --seed

이 흐름의 장점은 반복 가능성입니다. 내 컴퓨터에서 게시글 20개로 화면을 확인했다면, 팀원도 같은 명령으로 비슷한 상태를 만들 수 있습니다. 테스트 코드에서도 같은 방식으로 필요한 데이터를 준비할 수 있습니다.

단, db:seed는 이미 들어 있는 데이터를 자동으로 지워 주지 않습니다. 같은 시더를 여러 번 실행하면 게시글이 20개씩 계속 늘어날 수 있습니다. 깨끗한 상태에서 다시 시작하고 싶을 때는 로컬 개발 환경에서만 migrate:fresh --seed를 사용합니다.

컨트롤러는 아직 크게 바꾸지 않는다

이번 장의 목표는 데이터베이스 구조를 만드는 것입니다. 아직 게시글 목록 화면을 데이터베이스에서 읽어 오도록 완전히 바꾸지는 않아도 됩니다. 그 작업은 이어지는 Eloquent ORM 학습에서 진행합니다.

하지만 큰 흐름은 이미 정해졌습니다.

라우트는 요청의 입구를 담당합니다. 컨트롤러는 요청을 처리하고 데이터를 준비합니다. 뷰는 화면을 그립니다. 이제 여기에 데이터베이스가 추가되었습니다.

앞으로 게시글 목록 요청이 들어오면 흐름은 다음처럼 바뀝니다.

  1. 브라우저가 GET /posts 요청을 보냅니다.

  2. 라우트가 요청을 PostController@index로 보냅니다.

  3. 컨트롤러가 데이터베이스에서 게시글 목록을 가져옵니다.

  4. 컨트롤러가 게시글 데이터를 Blade 뷰에 전달합니다.

  5. Blade가 HTML을 만들고 브라우저에 응답합니다.

지금까지는 3번 자리에 배열이 있었습니다. 이제 그 자리에 데이터베이스가 들어갈 준비가 끝났습니다.

마이그레이션을 사용할 때의 습관

마이그레이션은 편리하지만 몇 가지 습관이 필요합니다.

첫째, 컬럼 이름은 의미가 분명해야 합니다. content보다 게시글 본문에는 body, 공개 여부에는 is_published, 발행일에는 published_at처럼 역할이 드러나는 이름이 좋습니다.

둘째, 되돌릴 수 있게 작성해야 합니다. up()에 컬럼을 추가했다면 down()에는 그 컬럼을 삭제하는 코드를 넣습니다. 되돌릴 수 없는 변경은 팀 개발과 배포에서 부담이 됩니다.

셋째, 실행된 마이그레이션을 함부로 고치지 않습니다. 이미 공유된 변경이라면 새 마이그레이션을 만들어 다음 변경으로 기록합니다.

넷째, 운영 서버에서 데이터 삭제 명령을 가볍게 실행하지 않습니다. fresh, refresh, 일부 rollback 명령은 데이터를 잃게 만들 수 있습니다. 명령을 입력하기 전에 현재 연결된 데이터베이스가 무엇인지 .env와 서버 환경을 확인해야 합니다.

다섯째, 테스트 데이터는 손으로 만들지 말고 시더와 팩토리로 만듭니다. 그래야 버그를 재현하기 쉽고, 화면 개발도 빠르게 반복할 수 있습니다.

이번 장에서는 데이터베이스 테이블을 코드로 정의하고 변경 이력을 관리하는 방법을 배웠습니다. 마이그레이션은 단순한 테이블 생성 도구가 아닙니다. 애플리케이션의 데이터 구조를 팀과 서버가 함께 공유하기 위한 약속입니다.

이어지는 Eloquent ORM으로 데이터 다루기에서는 이 테이블을 실제 PHP 코드와 연결합니다. Eloquent ORM을 사용해 posts 테이블의 데이터를 조회하고, 컨트롤러에서 배열 대신 데이터베이스 결과를 Blade 뷰로 전달해 보겠습니다.

책 목록

라라벨 학습서: 웹 기초부터 배포까지 배우는 Laravel 실전 입문

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기