Laravel에서 실시간 검색을 위한 완벽한 가이드

마지막 업데이트 : 5 12월 2025
  • Laravel을 사용하면 AJAX를 사용한 간단한 검색 엔진부터 Laravel Scout 및 Algolia, Meilisearch, Elasticsearch와 같은 외부 검색 엔진을 사용한 고급 전체 텍스트 검색까지 모든 것을 구현할 수 있습니다.
  • 가벼운 검색의 경우 Alpine.js를 사용하거나 기본 페치 요청을 사용하여 프런트엔드에서 필터링하면 서버 과부하를 방지하고 작은 목록에서 사용자 경험을 개선할 수 있습니다.
  • Laravel Scout는 다양한 검색 엔진과의 통합을 중앙화하고, 모델을 검색 가능한 것으로 표시하고, 인덱스를 관리하고, 균일하게 쿼리를 실행하는 것을 쉽게 해줍니다.
  • 엔진(SaaS, 오픈소스 또는 데이터베이스)을 선택할 때는 데이터 양, 검색의 복잡성, 프로젝트의 성능 및 유지 관리 요구 사항을 기준으로 해야 합니다.

Laravel에서 실시간 검색

Laravel을 사용하여 즉각적인 응답 속도를 가진 실시간 검색 엔진을 구축해야 할 때 , 수많은 접근 방식(fetch를 사용한 AJAX, jQuery, Alpine.js, Algolia 또는 Meilisearch를 사용한 Scout, 프런트엔드 필터링 등) 때문에 혼란스러울 수 있습니다. 하지만 다행히 Laravel 생태계는 복잡하지 않고 빠르고 원활한 검색 엔진을 구축하는 데 필요한 거의 모든 것을 이미 제공하고 있습니다.

이 글에서는 Laravel에서 다양한 유형의 실시간 검색을 구현하는 방법을 알아봅니다 . 고전적인 AJAX 자동 완성부터 Laravel Scout와 Algolia, Meilisearch, 데이터베이스 자체, 심지어 Elasticsearch와 같은 검색 엔진을 활용한 전체 텍스트 검색까지 다룹니다. 또한 소규모 데이터 세트를 다룰 때 브라우저에서 직접 데이터를 필터링하는 Alpine.js를 사용한 간편한 대안도 살펴봅니다.

Laravel의 실시간 검색은 무엇이고 기본 원리는 어떻게 작동합니까?

실시간 검색의 핵심 아이디어는 사용자가 텍스트 필드에 입력하는 즉시 쿼리가 실행되어 페이지를 새로 고침하지 않고도 결과가 업데이트되는 것입니다. 기술적으로는 Laravel 백엔드, 브라우저의 JavaScript, 그리고 JSON 형식의 데이터 교환이라는 세 가지 주요 구성 요소가 필요합니다.

한편으로, Laravel은 요청을 수신하고, 검색 매개변수(입력된 텍스트)를 해석하고, 데이터베이스를 조회하고, 일반적으로 JSON 형식의 구조화된 응답을 반환하는 서버 계층 역할을 합니다 . 이 응답은 성공, 오류 또는 검색 결과가 없음을 나타낼 수 있습니다.

반면 JavaScript는 검색 입력란에서 사용자 이벤트를 수신하고 , 백엔드로 비동기 요청(AJAX)을 보내고, 브라우저를 완전히 새로 고치지 않고도 반환된 데이터를 페이지에 렌더링하는 역할을 합니다. 이는 네이티브 fetch, jQuery AJAX 또는 Alpine.js와 같은 반응형 라이브러리를 사용하여 구현할 수 있습니다.

이 기본 메커니즘을 사용하면 몇 개의 레코드를 포함하는 간단한 자동 완성 기능부터 Laravel Scout와 같은 라이브러리 및 검색에 최적화된 외부 검색 엔진을 활용하여 관련성, 페이지네이션 및 필터 기능을 갖춘 고급 전체 텍스트 검색 엔진에 이르기까지 모든 것을 구축할 수 있습니다.

기본 실시간 검색 엔진을 위한 모델, 경로 및 컨트롤러

자바스크립트 작업을 시작하기 전에, 먼저 라라벨 부분을 잘 정리해 두어야 합니다. 검색에 사용할 엘로퀀트 모델, 명확한 라우트, 그리고 실시간 검색 로직을 처리하는 전용 컨트롤러가 필요합니다.

첫 번째 단계는 검색할 테이블을 나타내는 Eloquent 모델을 만드는 것입니다. 예를 들어, 국가 목록이 있는 테이블과 타임스탬프가 없고 일괄 할당이 가능한 Country 라는 매우 간단한 모델을 생각해 보세요.

검색을 위한 최소한의 Eloquent 모델 예시 :

namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Pais extends Model
{
use HasFactory;
protected $guarded = [];
public $timestamps = false;
}

이는 Pais 모델이 표준 Laravel 네임스페이스에 위치하고 , Model 클래스를 상속받으며, 보호된 배열을 비워두면 create() 메서드로 모든 필드에 값을 할당할 수 있음을 나타냅니다. `public $timestamps = false`를 사용하여 타임스탬프 기능을 비활성화하면 테이블에 `created_at` 및 `updated_at` 컬럼이 없는 경우 발생하는 문제를 방지할 수 있습니다.

다음 단계는 검색 엔진 표시와 AJAX 요청을 모두 처리할 경로를 정의하는 것입니다 . 가장 일반적인 방식은 뷰를 표시하는 GET 경로와 실시간으로 쿼리를 수신하도록 설계된 POST 경로를 결합하는 것입니다.

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\BuscadorController;

Route::get('/', function () {
return view('welcome');
});

Route::get('buscador', [BuscadorController::class, 'index']);
Route::post('buscador', [BuscadorController::class, 'buscar']);

루트 경로는 환영 화면을 반환하고, /search URL은 검색 기능을 위해 예약되어 있습니다 . 컨트롤러의 index() 메서드는 폼과 검색 입력란을 표시하고, search() 메서드는 브라우저에서 전송된 비동기 요청을 처리합니다.

컨트롤러에서 매우 실용적인 패턴을 구현할 수 있습니다. 오류 발생 시를 대비하여 기본 응답 배열을 준비하고 , 실제로 유효한 AJAX 요청이고 쿼리가 문제없이 실행될 때만 해당 배열을 덮어쓰는 것입니다.

컨트롤러는 다음과 유사한 구조를 가질 수 있습니다.

namespace App\Http\Controllers;

use App\Models\Pais;
use Illuminate\Http\Request;

class BuscadorController extends Controller
{
public function index()
{
return view('welcome');
}

public function buscar(Request $request)
{
$response = [
'success' => false,
'message' => 'Hubo un error',
];

if ($request->ajax()) {
$data = Pais::where('nombre', 'like', $request->texto.'%')
->take(10)
->get();

$response = [
'success' => true,
'message' => 'Consulta correcta',
'data' => $data,
];
}

return response()->json($response);
}
}

이 시점에서 전체 백엔드 사이클이 완성됩니다. 들어오는 AJAX 요청, 실제로 AJAX 요청인지 확인하는 과정, `where like`를 사용한 쿼리, 그리고 데이터베이스 과부하를 방지하기 위해 `take(10)`을 사용하여 결과 수를 적절한 수준으로 제한하는 과정이 포함됩니다. 응답은 항상 JSON 형식으로 전송되므로 프런트엔드 작업이 크게 간소화됩니다.

반응형 검색을 위한 Blade 뷰 및 JavaScript 페치

모델, 라우트, 컨트롤러가 준비되었으니 이제 보이는 부분, 즉 검색 필드가 있는 폼과 검색 결과를 표시하는 블록 , 그리고 백그라운드에서 요청을 처리하는 JavaScript 코드를 구축할 차례입니다.

Blade 뷰는 Laravel이 POST 요청을 검증하기 위해 삽입하는 CSRF 토큰 과 사용하기 편리한 검색 입력란에 의존하여 매우 간단하게 구현할 수 있습니다.

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<strong><meta name="csrf-token" content="{{ csrf_token() }}"></strong>
<title>Laravel</title>
</head>
<body>

<form action="" method="post">
<input type="search" name="texto" id="buscar">
</form>

<div id="resultado"></div>

<script>
window.addEventListener('load', function () {
const buscar = document.getElementById('buscar');
const resultado = document.getElementById('resultado');

buscar.addEventListener('keyup', function () {
fetch('/buscador', {
method: 'post',
body: JSON.stringify({ texto: buscar.value }),
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': document.head.querySelector('[name~="csrf-token"][content]').content,
},
})
.then(response => response.json())
.then(data => {
let html = '';
if (data.success) {
html += '<ul>';
for (let i in data.data) {
html += '<li>' + data.data[i].nombre + '</li>';
}
html += '<ul>';
} else {
html += 'No existen resultados';
}
resultado.innerHTML = html;
});
});
});
</script>

</body>
</html>

이 예제에서 스크립트는 검색 입력 필드의 키업(keyup) 이벤트를 수신 대기하므로 , 키를 누를 때마다 `/search` 경로로 데이터 가져오기 요청이 전송됩니다. 필드의 현재 텍스트는 JSON 형식으로 전송되며, AJAX 요청임을 나타내는 `X-Requested-With`와 같은 주요 헤더와 Laravel의 기본 보호 기능을 우회하기 위한 CSRF 토큰이 포함됩니다.

응답이 도착하면 JSON 형식으로 변환되고, 결과가 담긴 간단한 HTML 목록이 동적으로 생성됩니다 . 만약 쿼리 결과가 없는 경우에는 "검색 결과가 없습니다"와 같은 메시지가 표시됩니다. 이 모든 과정은 페이지를 새로고침하지 않고 사용자에게 자연스럽게 이루어집니다.

이 패턴은 키 입력 사이에 디바운스를 추가하거나 , 로더를 표시하거나, 네트워크 오류를 처리하여 오류 발생 시 인터페이스가 멈춘 것처럼 보이지 않도록 하는 등의 작은 UX 디테일을 통해 더욱 정교하게 다듬을 수 있습니다 .

jQuery를 사용하여 Laravel과 AJAX로 라이브 검색

fetch가 상당한 인기를 얻고 있지만, jQuery AJAX는 기존 프로젝트나 이미 fetch를 통합한 팀에서 여전히 매우 인기가 높습니다 . 기본 아이디어는 동일합니다. 사용자가 입력한 내용을 캡처하고, 비동기 요청을 보내고, DOM을 새로 고치는 것입니다.

Laravel에서 jQuery를 사용한 실시간 검색의 일반적인 워크플로는 다음과 같은 기본 단계를 포함합니다. 특정 경로를 정의하고, 전용 컨트롤러를 생성하고, 검색 입력란이 있는 Blade 뷰를 구축하고 , 마지막으로 입력 시 AJAX 요청을 트리거하는 jQuery 코드를 추가합니다.

이 과정은 다음과 같이 진행됩니다. 사용자가 입력을 시작하면 jQuery는 검색 문자열을 포함한 쿼리를 서버로 전송합니다 . Laravel은 데이터베이스에서 정보를 필터링하고 일치하는 결과를 담은 JSON 객체를 반환하며, jQuery는 일치하는 항목을 반영하여 페이지의 HTML 컨테이너를 업데이트합니다. 이 모든 과정은 단 몇 밀리초 만에 완료됩니다.

jQuery를 사용하면 AJAX 구문이 크게 단순화되고 , 프로젝트에 이미 라이브러리가 설치되어 있다면 코드를 읽기 쉽다는 장점이 있습니다 . 하지만 최신 JavaScript와 네이티브 fetch 기능을 활용할 수 있다면 불필요한 추가적인 의존성이 발생할 수 있습니다.

Alpine.js를 사용한 프런트엔드에서의 실시간 필터링 및 검색

표시할 데이터의 양이 비교적 적을 경우(예: 50개 미만 항목 ), 복잡한 검색 기능을 갖춘 백엔드를 구축하는 것이 항상 효율적인 것은 아닙니다. 이러한 경우에는 사용자가 입력하는 동안 서버에 요청을 보내지 않고 Alpine.js를 사용하여 브라우저에서 직접 필터링하는 것이 매우 편리한 방법입니다.

이 아이디어는 각 항목에 대한 검색 문자열(예: 이름, 설명, 카테고리를 소문자로)을 미리 계산하여 `data-search-text` 속성에 저장하고, Alpine.js가 검색 필드에 입력된 텍스트를 기반으로 항목을 표시하거나 숨기도록 처리하는 것입니다.

Alpine.js 컴포넌트는 다음과 유사한 구조를 가질 수 있습니다: filterItems

{
search: '',
hasResults: true,
selectedValue: '',
init() {
this.$watch('search', () => this.filterItems());
this.$nextTick(() => this.$refs.searchInput?.focus());
},
filterItems() {
const searchLower = this.search.toLowerCase().trim();
const cards = this.$el.querySelectorAll('.item-card');
let visibleCount = 0;

cards.forEach(card => {
const text = card.dataset.searchText || '';
const isVisible = searchLower === '' || text.includes(searchLower);
card.style.display = isVisible ? '' : 'none';
if (isVisible) visibleCount++;
});

this.hasResults = visibleCount > 0;
},
}

뷰에서 각 카드 또는 데이터 행에는 이미 소문자로 준비된 텍스트가 포함된 `data-search-text` 속성이 있으므로 필터는 JavaScript의 `includes()` 함수로 간소화되어 짧은 목록의 경우 매우 빠릅니다.

<input type="search" x-model="search" x-ref="searchInput" placeholder="Buscar..." />
<div>
<div class="item-card" data-search-text="formulario contacto simple">
<h3>Formulario de contacto</h3>
<p>Formulario de contacto simple</p>
</div>
</div>

또한, 현재 검색어에 대한 결과가 없을 때만 빈 상태 표시줄을 표시하여 사용자가 텍스트를 수정하거나 버튼을 눌러 검색을 빈 문자열로 초기화할 수 있도록 안내할 수 있습니다.

이 접근 방식에는 분명한 장점이 있습니다. 검색 중에 서버 호출이 전혀 없으며 , 상호 작용이 거의 즉각적이고, 로직이 로컬에 집중되어 디버깅이 용이합니다. 빠른 선택기, 항목 선택 모달 또는 Laravel 페이지에 포함된 소규모 카탈로그에 적합합니다.

Laravel Scout: 전문 엔진을 사용한 전체 텍스트 검색

빠르고 관련성 높으며 확장 가능한 전문 검색이 필요한 중요한 상황에서 Laravel Scout는 자연스러운 선택입니다. Scout는 Eloquent 모델을 Algolia, Meilisearch와 같은 검색 엔진, 데이터베이스, 인메모리 컬렉션, 심지어 외부 컨트롤러를 통한 Elasticsearch와도 쉽게 연결할 수 있도록 해주는 통합 레이어입니다.

Scout를 시작하려면 일반적으로 새 Laravel 프로젝트를 생성하거나 기존 프로젝트를 재사용하고 , Docker(예: Laravel Sail 사용)를 이용하여 프로젝트를 실행한 다음, Composer를 사용하여 라이브러리를 설치합니다. 설치가 완료되면 scout.php 설정 파일을 게시하고 사용하려는 드라이버에 따라 환경 변수를 조정합니다.

일반적인 워크플로는 Composer를 사용하여 Scout를 설치하고, 구성 파일을 게시한 다음, `.env` 파일에 `SCOUT_QUEUE=true`를 추가하여 인덱싱 큐를 활성화하는 것입니다 . 이렇게 하면 리소스 집약적인 작업이 백그라운드에서 처리되어 애플리케이션 응답 시간이 향상됩니다. 또한, `DB_HOST`가 사용 중인 데이터베이스를 가리키는지 확인해야 하며, 특히 Docker 컨테이너를 사용하는 경우 이는 매우 중요합니다.

Scout 검색에 참여할 모델을 지정하려면 `Searchable` 트레이트를 추가하여 검색 가능 모델로 명시적으로 표시 해야 합니다 . 예를 들어, `title` 필드가 있는 열차 목록을 나타내는 `Train` 모델이 있는 경우 다음과 같이 정의할 수 있습니다.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Train extends Model
{
use Searchable;

protected $fillable = ['title'];

public function searchableAs()
{
return 'trains_index';
}
}

`searchableAs` 메서드를 사용하면 모델에서 파생된 기본 이름 대신 검색 엔진에서 사용할 인덱스 이름을 사용자 지정할 수 있습니다. 이후 Scout는 선택한 드라이버에 따라 원격 또는 로컬 인덱스와 생성, 업데이트 및 삭제 작업을 동기화합니다.

Algolia를 활용한 Laravel Scout: 초고속 SaaS 검색

Algolia는 대규모 데이터에서 매우 빠르고 관련성 높은 검색을 제공하는 데 초점을 맞춘 SaaS 서비스입니다 . 인덱스, 관련성 규칙, 동의어 등을 관리할 수 있는 웹 패널을 제공하며, Scout 및 공식 PHP 클라이언트를 통해 Laravel과 원활하게 통합됩니다.

Scout에서 Algolia를 사용하려면 Composer를 사용하여 PHP 클라이언트를 설치하고, .env 파일에 자격 증명(애플리케이션 ID 및 관리자 API 키)을 등록한 다음, Scout가 이 엔진을 사용하도록 SCOUT_DRIVER=algolia로 구성해야 합니다 . 애플리케이션 ID와 관리자 키는 Algolia 대시보드에서 얻을 수 있습니다.

환경이 설정되면 컨트롤러에서 Train::search('text')->paginate(6) 와 같은 메서드를 직접 사용하여 인덱싱된 필드에 대한 검색을 수행하고 Blade 뷰로 전달할 준비가 된 페이지네이션된 Eloquent 형식으로 결과를 받을 수 있습니다.

예를 들어 , 모든 열차 목록을 표시하거나 titlesearch 매개변수가 전달되면 검색을 수행하는 인덱스 컨트롤러 와 새 열차를 인덱스에 삽입하는 create 메서드를 가질 수 있습니다 .

public function index(Request $request)
{
if ($request->has('titlesearch')) {
$trains = Train::search($request->titlesearch)->paginate(6);
} else {
$trains = Train::paginate(6);
}

return view('Train-search', compact('trains'));
}

public function create(Request $request)
{
$this->validate($request, ['title' => 'required']);
Train::create($request->all());
return back();
}

해당 뷰에서는 새 열차를 추가하는 폼 과 제출 시 검색을 실행하는 제목 검색 필드가 있는 또 다른 GET 폼을 결합할 수 있습니다. 그런 다음 열차 컬렉션을 반복하면서 Laravel에서 생성된 페이지네이션 링크를 활용하여 각 열차의 필드를 표 형식으로 표시하면 됩니다.

Meilisearch, 데이터베이스 및 컬렉션을 통한 스카우트

외부 서비스를 사용하지 않으려면 Meilisearch라는 오픈 소스 검색 엔진을 로컬 또는 자체 인프라에 배포할 수 있습니다. Scout는 Algolia와 매우 유사한 방식으로 Meilisearch와 통합되며, 드라이버를 변경하고 .env 파일에 MEILISEARCH_HOST 및 MEILISEARCH_KEY 변수를 추가하기만 하면 됩니다.

사용하려면 Meilisearch PHP 클라이언트를 설치하고, SCOUT_DRIVER=meilisearch 로 설정한 다음, MEILISEARCH_HOST를 인스턴스 URL(예: http://127.0.0.1:7700)로 지정하십시오. 이미 레코드가 있는 경우, 엔진에서 사용할 수 있도록 `php artisan scout:import "App\Models\Train"` 명령어를 사용하여 인덱싱할 수 있습니다.

규모가 작거나 중간 정도인 애플리케이션의 경우, Scout의 데이터베이스 드라이버를 선택할 수도 있습니다. 이 드라이버는 MySQL 또는 PostgreSQL 데이터베이스의 전체 텍스트 인덱스와 LIKE 문을 활용합니다. 이 경우 외부 서비스가 필요하지 않으며, Scout가 데이터베이스 자체를 검색 엔진으로 사용하도록 SCOUT_DRIVER=database로 설정하기만 하면 됩니다.

또 다른 흥미로운 옵션은 메모리 내 Eloquent 컬렉션과 연동되는 컬렉션 드라이버 입니다 . 이 엔진은 `where` 및 컬렉션 필터링 메서드를 사용하여 결과를 필터링하며, Laravel에서 지원하는 모든 데이터베이스와 호환됩니다. `SCOUT_DRIVER=collection`을 사용하여 활성화하거나, 더 구체적인 설정을 원할 경우 Scout 설정 파일을 수정하여 활성화할 수 있습니다.

Explorer를 사용하여 Elasticsearch와 통합

대규모 데이터 처리 및 실시간 분석이 필요한 검색 환경이라면 Elasticsearch가 대표적인 선택지입니다. Laravel 생태계에서 Scout와 Elasticsearch를 통합하는 최신 방법은 Explorer 컨트롤러를 사용하는 것입니다. Explorer 컨트롤러는 모델과 Elasticsearch 클러스터 간의 연결 다리 역할을 합니다.

일반적으로 Docker와 풍부한 docker-compose 파일을 사용하여 일반적인 서비스(Laravel, MySQL, Redis, Meilisearch 등) 외에도 Elasticsearch 및 Kibana 컨테이너를 실행합니다 . 그런 다음 Composer를 통해 jeroen-g/explorer 패키지를 설치하고 구성 파일을 게시하여 인덱싱할 모델을 지정합니다.

config/explorer.php 파일에서 indexes 키 아래에 모델을 등록할 수 있습니다. 예를 들어 App\Models\Train::class를 추가하면 됩니다 . 또한 .env 파일에서 드라이버를 Scout에서 Elasticsearch로 변경해야 합니다. SCOUT_DRIVER=elastic으로 설정하면 모든 것이 Elasticsearch를 가리키게 됩니다.

Train 모델 내에서 Explored 인터페이스를 구현하고 mappableAs 메서드 를 재정의해야 합니다 . 이 메서드는 인덱스로 전송할 필드 맵을 정의합니다. 최소한의 예시는 다음과 같습니다.

use JeroenG\Explorer\Application\Explored;
use Laravel\Scout\Searchable;

class Train extends Model implements Explored
{
use Searchable;

protected $fillable = ['title'];

public function mappableAs(): array
{
return [
'id' => $this->id,
'title' => $this->title,
];
}
}

여기에서 Scout 인터페이스를 사용하여 Elasticsearch에서 검색을 실행할 수 있으며 , Laravel 생태계를 벗어나지 않고도 매우 빠른 응답 시간과 Elasticsearch 엔진의 강력한 쿼리 기능을 활용할 수 있습니다.

fetch나 jQuery를 사용한 기본적인 자동 완성부터 Alpine.js를 사용한 프런트엔드 필터링, Laravel Scout와 다양한 드라이버를 사용한 전체 텍스트 검색에 이르기까지, Laravel은 프로젝트 규모, 필요한 성능, 유지 관리하려는 인프라에 맞춰 실시간 검색을 구현할 수 있는 광범위한 옵션을 제공합니다 .

  Python에서 Switch를 사용하는 방법: 예제와 대안을 포함한 확실한 가이드