라우팅
라우팅 (Routing)
Laravel 라우팅은 URL과 처리 로직을 연결하는 프레임워크의 심장이에요. 요청이 들어오면 어떤 코드를 실행할지 정해주는 '주소록'이라고 생각하면 쉬워요. 복잡한 설정 파일 없이도 URI와 콜백만으로 깔끔하게 정의할 수 있어서, 빠르게 시작할 수 있죠. 이 문서에서는 기본 라우팅부터 파라미터, 그룹, 모델 바인딩, 레이트 리밋까지 Laravel 12 기준으로 차근차근 살펴볼게요.
기본 라우팅 (Basic Routing)
가장 기본적인 라우팅은 URI와 클로저를 받아, 설정 파일 복잡성 없이 아주 단순하고 표현력 있게 동작을 정의해요. 아래 코드는 /greeting으로 GET 요청이 오면 "Hello World"를 돌려주는 라우트예요.
use Illuminate\Support\Facades\Route;
Route::get('/greeting', function () {
return 'Hello World';
});
라우트 파일의 기본 구조
모든 라우트는 프로젝트의 routes 디렉터리에 있는 파일에 정의돼요. 이 파일들은 bootstrap/app.php에 적힌 설정에 따라 자동으로 로드되죠. 그중 routes/web.php는 브라우저에서 접근하는 웹 인터페이스용 라우트를 담고, web 미들웨어 그룹이 적용되어 세션 상태와 CSRF 보호 같은 기능을 기본으로 얹어줘요.
컨트롤러를 쓸 땐 클로저 대신 [Controller::class, '메서드'] 배열 형태로 연결해요.
use App\Http\Controllers\UserController;
Route::get('/user', [UserController::class, 'index']);
API 라우트
무상태(stateless) API도 제공한다면 install:api Artisan 커맨드로 API 라우팅을 켤 수 있어요. 이 커맨드는 인증 가드로 유용하게 쓸 수 있는 Laravel Sanctum을 설치하고 routes/api.php 파일을 만들어줘요. routes/api.php의 라우트는 무상태이며 api 미들웨어 그룹과 /api URI 프리픽스가 자동 적용돼요.
php artisan install:api
프리픽스를 바꾸고 싶다면 bootstrap/app.php에서 apiPrefix 값을 조정하면 돼요.
라우터가 지원하는 HTTP 메서드
라우터는 모든 HTTP 동사에 응답할 수 있게 해줘요.
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::patch($uri, $callback);
Route::delete($uri, $callback);
Route::options($uri, $callback);
여러 메서드에 동시에 응답해야 한다면 match를, 모든 메서드에 응답해야 한다면 any를 써요.
Route::match(['get', 'post'], '/', function () {
// ...
});
Route::any('/', function () {
// ...
});
의존성 주입과 CSRF
라우트 클로저 시그니처에 필요한 타입을 힌트해 주면 Laravel 서비스 컨테이너가 자동으로 의존성을 주입해요. 예를 들어 현재 HTTP 요청을 받고 싶다면 Request를 타입 힌트하면 돼요.
use Illuminate\Http\Request;
Route::get('/users', function (Request $request) {
// ...
});
web 라우트 파일에 정의된 POST·PUT·PATCH·DELETE 라우트를 가리키는 HTML 폼에는 반드시 CSRF 토큰 필드를 넣어야 해요. 없으면 요청이 거부되니까 주의하세요.
<form method="POST" action="/profile">
@csrf
...
</form>
리다이렉트와 뷰 라우트
단순히 다른 URI로 리다이렉트만 하면 될 때는 Route::redirect로 간단하게 처리해요. 기본 상태 코드는 302이고, 세 번째 인자로 바꿀 수 있어요.
Route::redirect('/here', '/there');
Route::redirect('/here', '/there', 301);
Route::permanentRedirect('/here', '/there');
라우트가 단순히 뷰를 돌려주기만 한다면 Route::view를 써요. 뷰 이름과 함께 전달할 데이터 배열도 받을 수 있어요.
Route::view('/welcome', 'welcome');
Route::view('/welcome', 'welcome', ['name' => 'Taylor']);
라우트 목록 확인하기
애플리케이션에 정의된 모든 라우트를 훑어보고 싶을 때는 route:list 커맨드를 쓰면 돼요. -v 옵션을 붙이면 각 라우트에 적용된 미들웨어까지 보여주고, --path=api처럼 프리픽스로 필터링할 수도 있어요.
php artisan route:list
php artisan route:list -v
php artisan route:list --path=api
라우트 파라미터 (Route Parameters)
필수 파라미터
URI의 일부를 캡처해야 할 때는 중괄호 {}로 파라미터를 정의해요. 파라미터는 클로저/컨트롤러 인자에 순서대로 주입되기 때문에 인자 이름은 중요하지 않아요.
Route::get('/user/{id}', function (string $id) {
return 'User '.$id;
});
요청 객체 같은 의존성도 함께 필요하다면 의존성을 앞에, 파라미터를 뒤에 나열해요.
use Illuminate\Http\Request;
Route::get('/user/{id}', function (Request $request, string $id) {
return 'User '.$id;
});
선택(Optional) 파라미터
항상 URI에 존재하지 않을 수도 있는 값이라면 파라미터 이름 뒤에 ?를 붙이고, 변수에 기본값을 주면 돼요.
Route::get('/user/{name?}', function (?string $name = null) {
return $name;
});
정규식 제약 (Regular Expression Constraints)
파라미터 형식을 제한하고 싶다면 where 메서드로 정규식을 지정해요. 형식이 맞지 않는 요청은 404가 반환돼요.
Route::get('/user/{id}/{name}', function (string $id, string $name) {
// ...
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);
자주 쓰는 패턴은 헬퍼 메서드로 더 간단하게 쓸 수 있어요. whereNumber, whereAlpha, whereAlphaNumeric, whereUuid, whereUlid, whereIn이 대표적이에요.
Route::get('/user/{id}/{name}', function (string $id, string $name) {
// ...
})->whereNumber('id')->whereAlpha('name');
전역으로 항상 어떤 제약을 적용하고 싶다면 AppServiceProvider::boot에서 Route::pattern으로 정의하면, 그 이름의 파라미터를 쓰는 모든 라우트에 자동 적용돼요.
public function boot(): void
{
Route::pattern('id', '[0-9]+');
}
이름 있는 라우트 (Named Routes)
이름 있는 라우트는 특정 라우트를 가리키는 URL이나 리다이렉트를 편리하게 생성하게 해줘요. 라우트 정의에 name 메서드를 연결해서 이름을 붙이고, route() 헬퍼로 URL을 만들어요. 파라미터가 있으면 두 번째 인자로 값을 넘기면 올바른 자리에 자동 삽입돼요.
Route::get('/user/profile', function () {
// ...
})->name('profile');
// URL 생성
$url = route('profile');
// 리다이렉트 생성
return to_route('profile');
Route::get('/user/{id}/profile', function (string $id) {
// ...
})->name('profile');
$url = route('profile', ['id' => 1, 'photos' => 'yes']);
// http://example.com/user/1/profile?photos=yes
라우트 이름은 항상 유일해야 해요.
라우트 그룹 (Route Groups)
라우트 그룹을 쓰면 미들웨어 같은 공통 속성을 수백 개 라우트에 일일이 적용하지 않고 한 번에 공유할 수 있어요. 중첩된 그룹은 부모 그룹의 속성을 지능적으로 병합하는데, 미들웨어와 where 조건은 합쳐지고 이름과 프리픽스는 이어 붙어요.
Route::middleware(['first', 'second'])->group(function () {
Route::get('/', function () {
// first & second 미들웨어가 적용됨
});
});
URI 프리픽스는 prefix, 라우트 이름 앞에 공통 문자열을 붙일 땐 name 메서드를 써요. 이름 프리픽스는 점(.)을 포함해 정확히 지정된 대로 붙으니 주의하세요.
Route::prefix('admin')->group(function () {
Route::get('/users', function () {
// "/admin/users" URL과 매칭
});
});
Route::name('admin.')->group(function () {
Route::get('/users', function () {
// 라우트 이름: "admin.users"
})->name('users');
});
서브도메인 라우팅이 필요하면 domain 메서드로 서브도메인에 파라미터를 정의할 수 있어요. 이때 루트 도메인 라우트보다 먼저 등록해야 같은 URI 경로의 루트 라우트가 덮어쓰지 않아요.
Route::domain('{account}.example.com')->group(function () {
Route::get('/user/{id}', function (string $account, string $id) {
// ...
});
});
라우트 모델 바인딩 (Route Model Binding)
URL의 모델 ID를 라우트에 주입할 때마다 매번 DB 조회를 하는 대신, Laravel은 그 ID에 해당하는 모델 인스턴스를 라우트에 직접 주입해줘요. 이걸 모델 바인딩이라고 해요.
묵시적 바인딩
타입 힌트한 변수 이름이 URI 세그먼트 이름과 일치하면 Laravel이 자동으로 Eloquent 모델을 조회해 주입해요. 모델을 못 찾으면 404가 자동 반환되죠.
use App\Models\User;
Route::get('/users/{user}', function (User $user) {
return $user->email;
});
id가 아닌 다른 컬럼으로 조회하고 싶다면 {post:slug}처럼 파라미터 정의에 컬럼을 명시하거나, 모델의 getRouteKeyName을 오버라이드해요. 소프트 삭제된 모델까지 가져오려면 withTrashed()를 연결하세요.
Route::get('/posts/{post:slug}', function (Post $post) {
return $post;
})->withTrashed();
중첩 모델 바인딩에서는 관례에 따라 부모의 관계 이름을 추측해 자식 모델을 범위(scope) 안에서 조회해요. 아래 예시는 특정 User의 게시글만 slug로 찾아요. 관계를 자동으로 추론하고 싶지 않다면 scopeBindings()나 withoutScopedBindings()로 명시적으로 조절할 수 있어요.
Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
return $post;
});
묵시적 바인딩에서 모델을 못 찾았을 때의 동작은 missing 메서드로 커스터마이즈할 수 있어요. 예를 들어 목록 페이지로 리다이렉트시키는 식으로요.
Route::get('/locations/{location:slug}', [LocationsController::class, 'show'])
->name('locations.view')
->missing(function (Request $request) {
return Redirect::route('locations.index');
});
명시적 바인딩
관례 기반이 싫다면 Route::model로 파라미터와 모델 클래스를 직접 연결하고, 더 세밀한 로직이 필요하면 Route::bind로 해석 로직을 직접 정의해요. 둘 다 AppServiceProvider::boot에서 설정해요.
public function boot(): void
{
Route::model('user', User::class);
}
public function boot(): void
{
Route::bind('user', function (string $value) {
return User::where('name', $value)->firstOrFail();
});
}
폴백 라우트
어떤 라우트도 매칭되지 않을 때 실행할 라우트는 Route::fallback으로 정의해요. 일반적으로 처리되지 않은 요청은 예외 핸들러가 404 페이지를 렌더링하지만, 폴백 라우트를 두면 그 동작을 직접 제어할 수 있죠.
Route::fallback(function () {
// ...
});
레이트 리밋 (Rate Limiting)
Laravel은 특정 라우트나 그룹의 트래픽을 제한하는 강력하고 유연한 레이트 리밋 서비스를 제공해요. RateLimiter 파사드의 for 메서드로 리미터를 정의하고, Limit 클래스의 빌더 메서드로 한도를 구성해요.
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
public function boot(): void
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
}
by 메서드로 기준을 세분화(IP 주소별, 사용자 ID별 등)할 수 있고, 가능한 값을 중복되지 않게 하려면 프리픽스를 붙여줘요. 한도 초과 시 기본으로 429 응답이 반환되고, response 메서드로 직접 응답을 정의할 수도 있어요. 요청이 아니라 응답을 기준으로 제한하고 싶다면 after 메서드로 특정 상태 코드만 카운트하게 할 수 있어요.
RateLimiter::for('resource-not-found', function (Request $request) {
return Limit::perMinute(10)
->by($request->user()?->id ?: $request->ip())
->after(function (Response $response) {
// 열거 공격을 막기 위해 404 응답만 카운트
return $response->status() === 404;
});
});
정의한 리미터는 throttle 미들웨어로 라우트(그룹)에 붙여요. Redis를 캐시 드라이버로 쓴다면 withMiddleware에서 throttleWithRedis()를 호출해 레디스 기반 레이트 리밋을 쓸 수 있어요.
Route::middleware(['throttle:uploads'])->group(function () {
Route::post('/audio', function () {
// ...
});
});
폼 메서드 스푸핑과 라우트 캐시
HTML 폼은 PUT·PATCH·DELETE를 지원하지 않아서, 숨김 _method 필드로 실제 HTTP 메서드를 흉내 내야 해요. Blade의 @method 지시어로 편리하게 만들 수 있어요.
<form action="/example" method="POST">
@method('PUT')
@csrf
</form>
프로덕션 배포 시에는 route:cache로 라우트 캐시를 만들어 라우트 등록 시간을 크게 줄일 수 있어요. 새 라우트를 추가하면 캐시를 다시 생성해야 하므로, 배포 과정에서만 실행하는 게 좋아요.
php artisan route:cache
php artisan route:clear
더 알아보기 (Learn more)
- Laravel 공식 문서의 Routing 페이지
- Controllers 문서에서 컨트롤러 기반 라우트 살펴보기
- Middleware 문서에서 라우트에 붙이는 미들웨어 이해하기
- Blade 문서에서
@csrf·@method지시어 활용법 익히기