NAVER Search API를 활용한 단어 백과사전 검색 API 만들기

서론

최근 팀장님께 조언을 들은겸, 게임 하나를 간단하게 만들어 보려고 한다. 특정 페이지의 키워드를 기준으로 단어를 맞추는 게임을 react로 만드려고 하는데, 단어를 맞추기 앞서 단어의 정의 및 사전적 의미를 구해야 하기 때문에 관련하여 NAVER에서 제공하는 OPEN API 중 하나, Search API를 활용하여 네이버 백과사전 검색 데이터를 받아오는 API를 라라벨로 구현해 보자.

 

 

NAVER Developers 애플리케이션 등록

아래 공식 페이지에서 내 애플리케이션 등록을 통해 "검색" 애플리케이션을 만든 후 Client ID, Client Secret 두 가지 Key를 확보한다.

 

애플리케이션 - NAVER Developers

 

developers.naver.com

 

 

Laravel Route 설정 

나는 티스토리 API를 만들었던 프로젝트에 이어서 Naver API를 구현하기로 하였다. 티스토리 API가 궁금하다면 아래 포스팅에서 확인 가능하다.

 

티스토리 OPEN API 사용해서 블로그 데이터 추출하기

서론 Nest.js를 사용해서 블로그 만드는 토이 프로젝트를 시작하면서, 실제 데이터를 쌓거나 가져올 스토리지 개념의 저장소가 필요했다. 다른 Next.js 개발자 분들은 Notion을 스토리지 개념으로 많

min-nine.tistory.com

// routes/api.php
Route::prefix('v1')->group(function () {
    Route::prefix('tistory')->group(function (){
        Route::get('/',[\App\Http\Controllers\Api\TistoryController::class,'index'])->name('tistory.index');
        Route::get('/accessToken',[\App\Http\Controllers\Api\TistoryController::class,'accessToken'])->name('tistory.accessToken');
    });

    Route::prefix('naver')->group(function (){
       Route::get('/{keyword?}',[\App\Http\Controllers\Api\NaverController::class,'index'])->name('naver.index');
    });
});

이제 localhost:8000/api/v1/naver/{keyword} URL을 통해서 백과사전 검색 정보를 받아오도록 만들었다.

 

 

NaverController 생성

Laravel에서는 artisan command 를 사용하여 쉽게 컨트롤러를 생성할 수 있다.

php artisan make:controller Api/NaverController

나는 Controller와 Service로직을 분리해서 개발하는 것을 좋아해서 Controller의 코드는 최대한 간결하게 작성했다.

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Library\NaverLibrary;

class NaverController extends Controller
{
    public function index(?string $keyword)
    {
        if (!is_null($keyword)) {
            $naverLibrary = new NaverLibrary();
            $result = $naverLibrary->getSearchDictionary(keyword: $keyword);
            return response()->json($result);
        }
        return response()->json(['message'=>'we can not find search keyword. you must insert search keyword']);
    }
}

한 눈에 봐도 어떤 기능을 담당하는지 알게 작성하는 것이 클린코드라고 생각한다. 때문에 위 코드를 보면, NaverLibrary 객체의 getSearchDictionary()를 통해서 키워드에 관련된 검색 데이터를 받아와서 응답을 json으로 return한다.

 

 

Naver API 로직 관련 Service 객체 생성

Serivce, Library 등 문구는 본인의 마음대로 정해도 되지만, 나는 이번에는 Library라는 단어를 사용하여 NaverLibrary 클래스를 만들었다. 그 전에 TistoryLibrary에서 정의되어있던 makeJsonFile() 메서드와 initApiLibrary() 메서드를 공통으로 사용하기 위한 Class로 빼서 정의해줬다.

<?php

namespace App\Library;

use MingyuKim\PhpKafka\Libraries\ApiLibrary;

abstract class BaseLibrary
{
    /**
     * @param array $data
     * @param string|null $filePath
     * @param string $fileName
     * @return void
     * @description 지정한 filePath 및 fileName 장소에 data를 json형태로 저장한 후 파일 저장 경로를 반환한다.
     */
    protected function makeJsonFile(array $data, ?string $filePath, string $fileName): string
    {
        try {
            $fileFullPath = !is_null($filePath) ? $filePath . $fileName : $fileName;

            // 디렉토리가 존재하는지 확인하고, 없으면 생성
            if (!is_null($filePath) && !file_exists($filePath)) {
                mkdir($filePath, 0777, true); // true는 중첩된 디렉토리 생성을 허용
            }

            $jsonData = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
            file_put_contents($fileFullPath, $jsonData);

            // 파일의 전체 경로 반환
            return $fileFullPath;

        } catch (\Exception $exception) {
            die("makeJsonFile ERROR :: " . $exception->getMessage());
        }
    }


    protected function checkAlreadyExist(?string $filePath, string $fileName): bool
    {
        $fileFullPath = !is_null($filePath) ? $filePath . $fileName : $fileName;

        if (file_exists($fileFullPath)) {
            return true;
        }

        return false;
    }

    protected function initApiLibrary(string $method, string $url, mixed $data): void
    {
        $this->apiLibrary = ApiLibrary::getInstance();
        $this->apiLibrary->setMethod($method);
        $this->apiLibrary->setApiUrl($url);
        $this->apiLibrary->setRequestData($data);
    }
}

이제 위 추상 클래스를 상속받는 NaverLibrary 클래스를 생성한다.

<?php

namespace App\Library;

use MingyuKim\PhpKafka\Libraries\ApiLibrary;

class NaverLibrary extends BaseLibrary
{
    protected ApiLibrary $apiLibrary;
    protected string $outputDataPath;
    private string $client_id;
    private string $client_secret;

    public function __construct()
    {
        $this->client_id = config('naver.client_id');
        $this->client_secret = config('naver.client_secret');
        $this->outputDataPath = config('naver.output_data_path') ?? "";
    }


    /**
     * @param string $keyword
     * @return array
     * @description 백과사전 서치 정보 가져오기
     */
    public function getSearchDictionary(string $keyword): array
    {
        if (!$this->checkAlreadyExist(filePath: $this->outputDataPath, fileName: $keyword . ".json")) {

            $requestData = [
                'query' => $keyword
            ];

            $this->initApiLibrary(method: 'GET', url: 'https://openapi.naver.com/v1/search/encyc', data: $requestData);

            $result = $this->apiLibrary->callAPI();

            if (!$result || empty($result)) {
                die("getSearchDictionary :: result not found");
            }

            $result = json_decode($result, true);

            if (json_last_error() !== JSON_ERROR_NONE) {
                die("getSearchDictionary JSON decoding error: " . json_last_error_msg());
            }

            $this->makeDetailDictionary(keyword: $keyword, data: $result);

        } else {
            $result = file_get_contents($this->outputDataPath.$keyword.'.json');
            $result = json_decode($result,true);
        }
        return $result;
    }

    public function makeDetailDictionary(string $keyword, array $data)
    {
        try {
            $returnData = array();
            $returnData['outFileFullPath'][] = $this->makeJsonFile(data: $data, filePath: $this->outputDataPath, fileName: $keyword . ".json");

        } catch (\Exception $exception) {
            die("makeDetailCategory ERROR :: " . $exception->getMessage());
        }

        return $returnData;
    }

    protected function initApiLibrary(string $method, string $url, mixed $data): void
    {
        parent::initApiLibrary($method, $url, $data); // TODO: Change the autogenerated stub
        $header = [
            'Content-Type: application/json',
            'X-Naver-Client-Id: ' . $this->client_id,
            'X-Naver-Client-Secret: ' . $this->client_secret
        ];
        $this->apiLibrary->setHeader($header);
    }

}

Naver API 호출시에는 Request Header에 'X-Naver-Client-Id' 및 'X-Naver-Client-Secret' 값이 필요하다. 때문에 상속 받은 메서드를 오버라이딩 하여 header값을 변경해주는 로직을 추가로 작성하였다.

 

 

결과물

정상적으로 키워드 '구조체'에 관련된 백과사전 검색 데이터가 아래와 같이 응답 및 정상적으로 파일로 저장되는 것을 확인할 수 있다.