1 데이터와 정보 그리고 지식

데이터베이스를 이해하려면 먼저 데이터, 정보, 지식을 구분해야 한다. 세 개념은 서로 연결되어 있지만 의미와 활용 단계가 다르다.

구분 의미
데이터 현실 세계에서 관찰하거나 측정하여 수집한 정량적 또는 정성적 값
정보 데이터에 맥락과 의미를 부여해 의사 결정에 활용할 수 있도록 처리한 결과
지식 정보를 경험과 학습을 바탕으로 해석하여 판단과 문제 해결에 활용할 수 있는 이해

DB에 저장된 값은 데이터, 그 데이터를 조회/분석/가공 하면 정보, 정보를 해석/축적하면 지식

 

데이터의 형태와 특성

데이터는 구조에 따라 정형, 반정형, 비정형 데이터로 나눌 수 있다. 행과 열이 명확한 관계형 테이블이나 엑셀 자료는 정형 데이터, 키와 값의 구조를 가지지만 형식이 유연한 JSON과 XML은 반정형 데이터, 이미지·영상·자유 형식 문서는 비정형 데이터에 해당한다.

또한 특성에 따른 분류로는 범주형 데이터와 수치형 데이터로 구분할 수 있다. 범주형 데이터는 종류나 집단을 나타내므로 산술 연산보다 빈도와 비율을 살펴보는 데 적합하다. 수치형 데이터는 크기 비교와 산술 연산이 가능해 평균, 분산, 추세 분석 등에 활용된다. 데이터의 특성에 따라 적합한 분석 방법을 선택할 수 있다.

 

2 데이터베이스란 무엇인가

데이터베이스는 여러 사용자가 공유할 수 있도록 통합하여 저장한 운영 데이터의 집합이다. 이 정의에는 네 가지 중요한 개념이 포함된다.

공유 데이터: 특정 조직의 여러 사용자가 함께 소유하고 이용하는 데이터

통합 데이터: 불필요한 중복은 줄이고 필요한 중복만 허용하는 데이터

저장 데이터: 컴퓨터가 접근할 수 있는 저장 매체에 보관된 데이터

운영 데이터: 조직의 주요 기능을 수행하기 위해 지속적으로 필요한 데이터

데이터베이스는 실시간 접근성, 계속적인 변화, 동시 공유, 내용에 의한 참조라는 특성도 가진다.

실시간 접근성 : 수초 내에 질의 응답을 제공하는 것

계속적인 변화 : 삽입, 삭제, 갱신을 통해 항상 최신 상태를 유지하는 것

동시 공유 : 여러 사용자가 서로 다른 목적으로 동시에 동일 데이터를 이용할 수 있는 것

내용에 의한 참조: 데이터의 물리적 주소나 위치가 아니라 사용자가 요구하는 값으로 참조하는 것

 

3 데이터베이스 시스템의 핵심 구성

구성 요소 역할
데이터베이스 통합·공유되는 운영 데이터를 실제로 저장한다.
DBMS 사용자와 데이터베이스 사이에서 저장, 조회, 변경, 보안, 복구를 관리한다.
데이터 모델 현실의 데이터를 데이터베이스 구조로 변환하는 논리적 기준을 제공한다.
사용자 일반 사용자, SQL 사용자, 응용 프로그래머, DBA 등 목적에 따라 데이터를 활용하고 관리한다.
인터페이스 SQL과 응용 프로그램을 통해 사용자 요청을 DBMS에 전달한다.

 

4 데이터베이스 시스템의 발전

1단계 : 동네서점

2단계 : 초기 전산화(+로컬 컴퓨터)

3단계 : 데이터베이스 시스템 도입(+원격통신)

4단계 : 홈페이지 구축(+인터넷)

5단계 : 인터넷 쇼핑몰로 확장

 

5 파일 시스템과 DBMS의 차이

초기 프로그램은 데이터를 프로그램 내부의 배열이나 개별 파일에 저장했다. 데이터가 적을 때는 간단하지만, 데이터 구조가 바뀌거나 여러 프로그램이 같은 파일을 함께 사용할 때 문제가 커진다. 데이터가 중복되고 서로 다른 값으로 갱신될 수 있으며, 보안·공유·복구 기능도 각 프로그램이 따로 구현해야 하기 때문이다.

DBMS를 사용하면 데이터와 응용 프로그램을 분리해 관리할 수 있다. 응용 프로그램은 저장 위치나 세부 형식을 직접 다루는 대신 DBMS에 SQL로 요청한다. 이 구조는 데이터 독립성과 일관성을 높이고, 중복을 통제하며, 프로그램 개발과 유지보수를 단순하게 한다.

항목 파일 시스템 DBMS
데이터 정의 응용 프로그램 DBMS
데이터 저장 파일시스템 데이터베이스
데이터 접근 방법 응용 프로그램이 파일에 직접 접근함 응용 프로그램이 DBMS에 파일 접근을 요청함
사용 언어 자바, C++, C등 자바, C++, C등과 SQL
CPU/주기억장치 사용 적음 많음

 

DBMS의 장점과 한계

DBMS의 장점은 데이터 중복 통제, 데이터 독립성, 동시 공유, 보안 향상, 무결성 유지, 표준화, 장애 복구, 응용 프로그램 개발 비용 절감으로 정리할 수 있다. 반면 도입 비용이 들고 백업과 장애 복구가 복잡하며, 중앙 집중 관리에 따른 취약점도 존재한다. 대규모 동시 접속이나 복잡한 질의에서는 성능 저하가 발생할 수 있어 설계, 운영, 최적화와 튜닝을 담당할 전문 인력이 필요하다.

 

6 DBMS와 정보 시스템의 발전

정보 시스템은 개별 파일 중심의 처리에서 데이터베이스 시스템, 웹 데이터베이스 시스템, 분산 데이터베이스 시스템으로 발전해 왔다. 초기에는 한 컴퓨터 안에서 도서 검색이나 매출 관리를 처리했다면, 통신 기술과 인터넷의 발전 이후에는 본점과 지점, 거래처와 고객까지 연결되었다. 오늘날에는 여러 서버와 지역에 데이터를 분산하여 더 많은 사용자의 요청을 처리한다.

DBMS 자체도 데이터 표현 방식과 요구에 따라 발전했다. 1세대의 계층형·네트워크 DBMS는 포인터를 이용해 관계를 표현했고, 2세대의 관계형 DBMS는 데이터를 테이블 형태로 표현하여 이해와 개발을 쉽게 했다. 이후 객체지향 개념을 결합한 객체지향·객체관계 DBMS가 등장했으며, 대규모 분산 환경과 비정형 데이터 처리를 위해 NoSQL과 NewSQL이 발전했다.

세대 대표 유형 핵심 특징
1세대 계층형, 네트워크형 트리나 그래프 구조와 포인터로 관계 표현
2세대 관계형 행과 열로 구성된 테이블과 속성값으로 관계 표현
3세대 객체지향, 객체관계형 객체 식별자와 상속·캡슐화 개념 활용
4세대 NoSQL, NewSQL 유연한 구조와 분산 확장성, 관계형의 안정성 결합

 

7 데이터베이스 시스템의 구성

- 데이터베이스/DBMS/데이터 모델

- 데이터베이스 사용자

- 인터페이스

   * 데이터베이스 언어(SQL)

   * 응용프로그램

- 시스템 카탈로그

 데이터 베이스의 모든 정보를 담고 있는 종합 저장소

 데이터 사전 + 데이터 디렉토리

- 데이터 사전

 메타데이터의 개념 : 데이터에 관한 데이터, 데이터베이스의 구조와 뜻을 설명하는 메타데이터 저장소

 특징 : 스키마, 테이블, 뷰, 인덱스, 제약조건, 사용자 권한 정보등이 저장됨

 읽기 전용 : DBMS가 스스로 생성하고 갱신함. 사용자는 일반 테이블처럼 select로 조회는 가능. 직접 변경은 불가능.

- 데이터 디렉터리

 사용자와 DBMS는 모두 접근 가능한 영역(사전)과 시스템(DBMS)만 내부적으로 접근하는 영역(디렉터리)의 차이

 데이터 디렉토리 : 데이터 사전에 있는 내용에 실제로 접근하는 위치와 경로 정보를 관리하는 시스템

 

8 SQL과 데이터베이스 사용자

SQL은 사용자와 DBMS가 의사소통하는 언어다. SQL은 용도에 따라 데이터 정의어, 데이터 조작어, 데이터 제어어로 구분한다.

DDL: 테이블 등 데이터베이스 구조를 정의. CREATE, ALTER, DROP 등

DML: 데이터를 검색·삽입·수정·삭제. SELECT, INSERT, UPDATE, DELETE 등

DCL: 데이터 사용 권한과 내부 규칙을 관리. GRANT, REVOKE 등

가장 기본적인 데이터 조회는 SELECT-FROM-WHERE 구조로 이루어진다. SELECT는 확인할 열, FROM은 대상 테이블, WHERE는 행을 선택할 조건을 지정한다. 데이터베이스를 직접 사용하는 정도는 역할마다 다르다. 일반 사용자는 응용 프로그램을 통해 데이터를 이용하고, SQL 사용자는 필요한 업무를 질의로 처리한다. 응용 프로그래머는 사용자가 이용할 프로그램을 만들며, DBA는 데이터베이스의 구조, 권한, 보안, 성능과 운영을 종합적으로 관리한다.

 

9 데이터 모델과 관계 표현

데이터 모델은 현실 세계의 데이터를 데이터베이스 구조로 변환하기 위한 논리적 가이드라인이다. 데이터가 어떻게 구조화되고 저장되며 서로 어떤 관계를 가지는지를 결정한다. 계층형과 네트워크형 모델은 포인터로 관계를 표현하므로 실행 속도는 빠를 수 있지만 프로그램이 구조에 강하게 의존한다. 관계형 모델은 공통 속성값을 이용해 테이블 사이의 관계를 표현하므로 개념이 단순하고 개발이 쉽다. 객체 모델은 객체 식별자를 사용하며 객체지향 언어의 상속과 캡슐화 개념을 활용할 수 있다. 현재 가장 널리 사용되는 것은 관계 데이터 모델이다.

 

10 3단계 스키마 구조와 데이터 독립성

ANSI/SPARC가 제안한 3단계 데이터베이스 구조는 데이터베이스를 바라보는 관점을 외부 단계, 개념 단계, 내부 단계로 나눈다. 이 구분의 핵심 목적은 복잡한 저장 구조를 사용자에게 그대로 노출하지 않고, 각 단계의 변경이 다른 단계에 미치는 영향을 줄이는 데 있다.

단계 관점 스키마의 의미
외부 단계 개별 사용자 사용자나 응용 프로그램이 필요로 하는 데이터의 논리적 부분. 여러 외부 스키마가 존재할 수 있다.
개념 단계 조직 전체 전체 데이터베이스의 논리 구조, 관계, 제약조건, 보안 정책과 접근 권한을 정의한다. 일반적으로 하나만 존재한다.
내부 단계 저장 장치 레코드 구조, 필드 크기, 인덱스, 배치와 압축 등 실제 저장 방법을 정의한다. 하나의 내부 스키마가 존재한다.

 

스키마와 인스턴스

스키마는 데이터베이스에 저장되는 데이터 구조와 제약조건을 정의한 설계도다. 반면 인스턴스는 특정 시점에 스키마에 따라 실제로 저장된 값이다. 학생 테이블의 열 이름과 자료형은 스키마이고, 각 학생의 학번·이름·학년 값은 인스턴스라고 볼 수 있다. 스키마는 비교적 안정적이지만 인스턴스는 데이터의 삽입, 수정, 삭제에 따라 계속 변한다.

 

논리적 데이터 독립성과 물리적 데이터 독립성

DBMS는 외부 스키마와 개념 스키마 사이, 개념 스키마와 내부 스키마 사이의 대응 관계를 매핑으로 관리한다. 이 매핑 덕분에 하위 단계가 변경되어도 상위 단계에 미치는 영향을 줄일 수 있다.

논리적 데이터 독립성: 개념 스키마가 변경되어도 외부 스키마와 응용 프로그램이 영향을 받지 않도록 하는 성질. 예를 들어 테이블에 새로운 속성을 추가해도 기존 사용자의 조회가 그대로 동작하도록 할 수 있음.

물리적 데이터 독립성: 내부 저장 구조가 변경되어도 개념 스키마가 영향을 받지 않도록 하는 성질. 예를 들어 인덱스 구조나 파일 배치 방식을 바꾸어도 사용자에게 보이는 테이블 구조는 동일하게 유지됨.


느낀점

현업에서 데이터베이스를 쓰기 때문에 익숙해서 놓치고 있던 개념들을 한번씩 더 훑고가는 계기가 되어 유익했다.

기존에는 RDBMS만 썼었는데, 다양한 DBMS를 경험해보고 장단점을 체험해보고 싶다

 

<문제풀이>

1) 다음 중 데이터베이스의 4대 특성에 해당하지 않는 것은?

① 실시간 접근성

② 계속적인 변화

③ 동시 공유

④ 물리적 주소에 의한 참조

 

2) 파일 시스템과 비교했을 때 DBMS를 사용하는 장점으로 가장 적절한 것은?

① 데이터 중복을 증가시킨다.
② 데이터 독립성을 확보할 수 있다.
③ 모든 장애를 자동으로 방지한다.
④ 전문적인 관리가 필요하지 않다.

 

3) SQL에서 테이블을 생성하거나 구조를 변경할 때 사용하는 언어는 무엇인가?

① 데이터 정의어 DDL
② 데이터 조작어 DML
③ 데이터 제어어 DCL
④ 데이터베이스 관리 시스템 DBMS

 

4) 다음 중 ‘정보’에 대한 설명으로 가장 적절한 것은?

① 현실 세계에서 관찰하거나 측정하여 수집한 값이다.
② 데이터에 맥락과 의미를 부여하여 의사결정에 활용할 수 있도록 처리한 결과물이다.
③ 경험과 학습을 통해 축적되어 문제 해결에 직접 활용되는 이해이다.
④ 데이터베이스의 구조와 제약조건을 정의한 것이다.

 

5) 인덱스 구조나 데이터의 물리적인 저장 방식을 변경해도 개념 스키마가 영향을 받지 않는 성질은 무엇인가?

① 논리적 데이터 독립성
② 데이터 무결성
③ 동시 공유성
④ 물리적 데이터 독립성

 

https://velog.io/@youngjun_10/BackEnd-기술-면접-질문-정리

 

BackEnd 기술 면접 질문 정리

객체지향 프로그램 구현에 필요한 객체를 파악하고 각각의 객체들의 역할이 무엇인지를 정의하여 객체들 간의 상호작용을 통해 프로그램을 만드는 것을 말한다. 절차지향 프로그래밍이란? 프

velog.io

https://dncjf64.tistory.com/479

 

3개월 동안 면접 25군데 본 백엔드 경력 이직 후기

안녕하세요.이번글은 퇴사 후 3개월 동안 면접을 25군데 보고 첫 경력 이직에 성공한 후기를 남기려고 합니다.전체 과정에서 이직을 어떻게 준비했어야 할지, 남은 커리어를 어떻게 일해야 할지

dncjf64.tistory.com

https://jeong-pro.tistory.com/240

 

자바 백엔드 4년차 N사 경력 면접 후기(부제 : 면접을 이끄는 건 누구인가?)

쓸데없는 서론 지금 다니는 회사 동료분들 중 몇 분이 내 블로그를 알고 있기 때문에 면접 봤다는 이야기가 알려지면 좋을 것이 하나도 없지만, 면접이 주는 영감? 동기부여?가 있기도 하고 공유

jeong-pro.tistory.com

https://spartacodingclub.kr/blog/2024-backend-jobinterview-question

 

백엔드 면접 질문 문제은행 - 개발자 면접 준비 101

백엔드 면접 질문 문제은행 20선 - 1) SQL과 NoSQL 데이터베이스의 차이점은 무엇인가요? 2) 가기 전 이건 무조건 생각하고 가세요. 만약...

spartacodingclub.kr

 

인프라에 대한 첫글 ㅎㅎ

배포 전략의 종류를 몇가지 알아보자

 

1. Recreate

모든 서버 중지 후에 새로운 버전 배포 후 다시 서비스를 올리는 방식

다운타임이 발생하는 배포전략이라 잘 사용되지는 않음

 

2. Rolling

여러대의 서버가 있을때 새로운 버전의 서비스를 서버마다 순차적으로 배포

한대의 서버를 셧다운하고, 로드밸런서로 나머지 하나의 서버로 트래픽을 다 밀어넣고

셧다운 된 서버에 새로운 버전을 배포 한 후 서비스를 올리는 방법.

하나 서버가 완료되면 나머지 하나의 서버도 동일한 방식으로 배포한다.

 

3. blue/green

blue는 구버전, green은 신버전을 의미하며

새로운 버전을 모두 배포하고, L4에서 새로운 버전의 서버로 거래가 유입되도록 하는 방법.

 

4. canary

특정 서버에만 선 배포를 진행하여 오류 여부를 확인하고 문제가 없다고 판단되면 모든 서버에 새로운 버전으로 

배포하는 방식이다.

문제발생시 선배포한 서버만 롤백하면 되어 롤백이 간단하다.

 

 

 

firebase with coroutines

문제 상황

firebase를 이용하다가 보면 라이브러리에서 제공하는 대부분의 메서드는 listener를 통해 비동기로 응답을 보내주고, 그 응답을 가공하여 사용해야 하는 경우가 많다

예를 들어

fun subscribeWalkingDistanceData(successCallback : () -> Unit, failureCallback : () -> Unit){
    Fitness.getRecordingClient(GlobalApplication.getApplicationContext(),mGoogleSignInAccount).subscribe(DataType.TYPE_DISTANCE_DELTA)
.addOnSuccessListener{
	Log.d(TAG,"successfully subscribe distance delta")
	successCallback()
}.addOnFailureListener{
		Log.e(TAG,"there was a problem subscribing walking distance data${it.localizedMessage}")
	failureCallback()
	}
}

위와 같이 유저가 이동한 거리에 대한 데이터를 subscribe를 하기 위해 다음과 같은 메서드를 작성했다면, 각각 성공 실패 리스너를 통해 firebase가 어떤 응답을 주는가에 따라서 매개변수로 받은 콜백함수를 실행시키는 형태로 코드를 작성 할 수 있다.

그런데 이렇게 코드를 작성하다 보면 콜백지옥의 문제가 생긴다.

depth가 하나일 때는 무난하게 callback함수를 매개변수로 넘겨 실행 시켜도 문제가 없지만

depth가 깊어질 수록 매개변수로 콜백을 계속 물고 들어가기 때문에 점점 복잡해지는 문제가 생긴다.

사실 이전에는 콜백으로 처리하였는데, 이번에 MVP패턴을 적용하면서 함수를 단순한 기능의 형태로 쪼개다보니 파이어베이스에서 받아온 응답 데이터를 그대로 return 해줘야 하는 함수들을 만들기 시작했고, 이에 다른 방법을 찾아보게 되었다.

해결 방법

구글링 결과 이를 해결하기 위해서 파이어베이스 메서드를 비동기처리하는 라이브러리를 추가하면 된다는 사실을 알게되었다.

https://github.com/Kotlin/kotlinx.coroutines/tree/master/integration/kotlinx-coroutines-play-services

  1. 모듈 수준 gradle에 dependency를 추가해주자

implementation **'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.2'**

  • 현재기준 1.6.2가 가장 최신 버전이다.
  1. dependency를 추가한 후 firebase에서 제공하는 라이브러리 안의 함수를 호출할 때 .await()를 사용할 수 있게 되어 응답이 올 때 까지 대기 할 수 있게 된다.
  2. 코드를 바꿔보자.
/*하루동안 걸은 거리 관련 함수 */
//피트니스 데이터(걸은 거리)
suspend fun subscribeWalkingDistanceData(){
    Fitness.getRecordingClient(GlobalApplication.getApplicationContext(),mGoogleSignInAccount).subscribe(DataType.TYPE_DISTANCE_DELTA)
     .addOnSuccessListener{
		}.addOnFailureListener{
	}

val response = Fitness.getRecordingClient(GlobalApplication.getApplicationContext(),mGoogleSignInAccount)
        .subscribe(DataType.TYPE_DISTANCE_DELTA).await()
}

위처럼 리스너에 콜백 함수를 지정하는 방식에서 밑에 await를 통해 응답이 올때까지 대기 한 후 response라는 변수에 응답으로 온 데이터를 할당하는 방식으로 바꿀 수 있다.

google play api는 google play service의 하나의 파트 임.

google fit api는 안드로이드 4.1 이상부터 호환.

Google Fit API 장점

  • 거의 실시간의 히스토리 데이터를 적은 에너지의 블루투스 디바이스로부터 가져옴
  • 활동들을 기록 할 수 있음
  • 데이터를 세션과 연관시킬 수 있음
  • 피트니스 목표를 설정 할 수 있음

sensor data

  • 유저의 하루 활동 관련한 정보들을 앱에서 제공한다면 (예를 들어 하루동안의 걸음수), sensor data는 사용자의 행동을 거의 실시간으로 보여주는데 사용이 가능함.

record data

  • 앱이 꺼져있는 상황에서도 계속해서 데이터를 적재 할 수 있는 subscribe 메서드를 제공함

historical data

  • 만약 유저가 과거의 활동들로부터 피트니스 데이터를 보여주기를 원한다면 history api를 사용하면 됨
  • 데이터 subscribe가 선행 되어야 historical data를 읽을 수 있음 ( 도큐먼트가 친절하지 않아서 테스트 후에 알게 됨), 데이터가 없으면 빈 리스트로 응답이 옴.

session data

  • 개발자가 일정한 시간의 세션을 설정하고 그 설정안에 들어가는 범위의 데이터를 가져올 수 있음

내가 하고자 하는 것

  1. subscribe를 통해 앱 사용자의 활동내역(현재는 걸음 수, 이후에 추가할 예정)들을 클라우드 형태의 google fit store에 앱을 종료했을 때도 마찬가지로 계속해서 저장해야 함.
  2. 데이터가 있는 날부터 일(00시~24시) 기준으로 걸음 수 데이터를 보여줄 수 있어야 함
  3. 배터리 최적화, 잠자기 모드 등 예기치 못한 상황에서도 정상적으로 데이터를 가져와야 함
  4. 데이터는 자정 기준으로 새로 시작함

사전 준비

  1. google fit api 는 Google API Console 에 프로젝트를 등록 및 client ID 를 발급 받은 후 사용해야함.
  2. client ID 발급 후 사용할 library에서 google fit api 를 사용 설정 해야 함.
  3. 테스트를 위해서 OAuth 동의 화면 탭의 테스트 사용자에 테스트를 수행 할 사용자 정보를 등록해야 함.
  4. 모듈 범위의 gradle 에
plugin {
    id("com.android.application")
}

...

dependencies {
        implementation("com.google.android.gms:play-services-fitness:21.1.0")
        implementation("com.google.android.gms:play-services-auth:20.2.0")
}

다음 과 같이 dependency를 추가하여 필요한 라이브러리를 다운로드 함

  1. https://developers.google.com/fit/android/get-started 를 참고하면 더 자세히 setup에 관해 나와 있음

구현 방법

  1. fitness option 객체를 만들어 내가 사용하고자 하는 data들을 추가함
funcreateFitnessStepOptions() : FitnessOptions{
	val fitnessOptions = FitnessOptions.builder()
        .addDataType(DataType.TYPE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
        .addDataType(DataType.AGGREGATE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
        .addDataType(DataType.TYPE_DISTANCE_DELTA, FitnessOptions.ACCESS_READ)
        .addDataType(DataType.AGGREGATE_DISTANCE_DELTA, FitnessOptions.ACCESS_READ)
        .build()
	return fitnessOptions
}

걸음수와 지금까지 걸은 거리에 대한 데이터를 얻기 위해 위처럼 4개의 fitness data type을 추가함.

  1. google account를 가져옴
fun getAccount(fitnessOptions: FitnessOptions) : GoogleSignInAccount{
	val account = GoogleSignIn.getAccountForExtension(GlobalApplication.getApplicationContext(),fitnessOptions)
	return account
}

getAccountForExtension 메서드를 이용하여 추가한 데이터에 대한 인증을 사용하는 google signin account를 얻음.

  1. google fit api를 사용하기 위한 권한 요청
fun checkHasGrantedDataAccess(account: GoogleSignInAccount, fitnessOptions: FitnessOptions, callback: () -> Unit){
    Log.i(TAG,"checkHasGrantedDataAccess${account.id}, fitnessOptions${fitnessOptions}")
if(!GoogleSignIn.hasPermissions(account, fitnessOptions)){
	//해당 google id가 permission이 허용되었는지 체크함.
	Log.d(TAG,"!GoogleSignIn.hasPermissions")
	GoogleSignIn.requestPermissions(mActivity,GOOGLE_FIT_PERMISSIONS_REQUEST_CODE, account, fitnessOptions)
  }else{
	  callback()
  }
}

account 가 접근하고자 하는 google fit api data에 대한 권한이 있는지 체크하고, 권한이 있다면 callback함수를 실행함. 없다면 권한을 요청함.

  1. 걸음 수 / 걸은 거리 데이터 구독 시작
//피트니스 데이터(걸음 수)구독셋팅
fun subscribeStepCountData(successCallback: () -> Unit?, failureCallback: () -> Unit?){
    Fitness.getRecordingClient(GlobalApplication.getApplicationContext(),mGoogleSignInAccount)
  .subscribe(DataType.TYPE_STEP_COUNT_DELTA)// no scopes are specified.
	.addOnSuccessListener{
	Log.i(TAG,"successfully subscribe step count data!!")
	  successCallback()
	}.addOnFailureListener{
	Log.w(TAG,"there was a problem subscribing step count data${it}")
	  failureCallback()
	}
}

  1. 걸음 수를 원하는 날짜부터 가져옴 (수정 중)
fun getStepCountForDays(days : Int){

	val cal = Calendar.getInstance()
	val now = Date()
	    cal.time= now
	val endTime = cal.timeInMillis
	
	cal.set(Calendar.HOUR_OF_DAY, -days)
	    cal.set(Calendar.MINUTE, 0)
	    cal.set(Calendar.SECOND, 0)
	    cal.set(Calendar.MILLISECOND, 0)
	
	val startTime = cal.timeInMillis
	
	val readRequest = DataReadRequest.Builder()
	        .aggregate(DataType.TYPE_STEP_COUNT_DELTA, DataType.AGGREGATE_STEP_COUNT_DELTA)
	        .bucketByTime(1, TimeUnit.DAYS)
	        .setTimeRange(startTime, endTime, TimeUnit.SECONDS)
	        .build()
	
	    Fitness.getHistoryClient(mActivity,mGoogleSignInAccount)
	    .readData(readRequest)
	    .addOnSuccessListener{
	response->
	for(dataSetinresponse.buckets.flatMap{ it.dataSets}) {
	  Log.i(TAG,"dataSet ==${dataSet}")
		//원하는 로직 추가 }
	}.addOnFailureListener{
		//실패 처리로직 추가
	}
}

원하는 날부터 현재까지 일 단위의 걸음 수 데이터를 가져오는 로직

결과

테스트를 해보려고 구글 피트니스 앱과 데이터를 비교한 결과 데이터가 잘 조회 되는 것을 확인하였음. 이 과정에서 한번 더 고민하고 액션을 취했던 부분은 혹시나 하는 마음에 매일 자정이 되기 찰나의 순간에 하루동안의 데이터를 조회해 오는 메서드 readDailyTotal 를 호출하여 데이터를 저장하는 방법과, DataReadRequest 객체에 startTime과 endTime을 설정해 그 기간동안의 data를 가져와 사용하는 방법 간 데이터 결과에 차이가 있는가에 대한 테스트를 했고 데이터간 차이가 없는 것을 확인하고 후자의 방식으로 채택했음. 그러나 startTime과 endTime을 잘못 설정하면 자정이 기준이 아니고 현재 시간을 기준으로 하루가 책정되기 때문에 startTime과 endTime을 설정을 자신이 필요한 데이터에 따라 정확히 할 필요가 있음.

Chronometer를 이용한 스톱워치 구현

<Chronometer
android:id="@+id/timer"
android:layout_width="200dp"
android:layout_height="40dp"
android:textSize="16sp"/>

xml파일에 다음과 같이 Chronometer 를 추가함.

나는 start, pause, stop 버튼에 각각 시작, 일시정지, 중지 기능을 추가해 넣었음.

전역변수로는 일시 정지를 누른 시간을 저장함

fun startTimer(){
	timer.base= SystemClock.elapsedRealtime() +pauseTime
  timer.start()
}

fun pauseTimer() {
	pauseTime=timer.base- SystemClock.elapsedRealtime()
	timer.stop()
}

fun resetTimer(){
	pauseTime= 0L
	timer.stop()
}

각각 버튼의 클릭리스너에 기능에 맞는 함수를 호출하면 됨.

여기서 timer는 Chronometer 임**.**

1. 하고자 하는 기능?

사용자가 자신이 갔던 경로를 트랙킹하여 지도에 표시해 타 사용자들에게 공유하는 기능

2. 프로세스

  1. 해당 화면 접근 시 런타임에 위치 권한 체크
  2. 위치 권한 허용 시 (3)으로 이동, 거부시 (5)로이동
  3. start 버튼 선택 시 현재 내 위치로 이동 및 tracking시작
  4. 설정한 시간 간격으로 위치를 얻어와 지도 위에 선으로 표시
  5. stop버튼 선택 시 tracking 종료
  6. 위치 권한 재 요청, 2번 거부 시 수동으로 앱 설정에 가서 허용하라는 다이얼로그 표시

3. 구현 방법

3-1) 런타임 위치 권한 체크

** 위치 권한 체크 시 유의할 점

내가 구현하고자 했던 기능은 앱이 백그라운드 상태에 가 있을 때에도 사용자의 위치를 추적해야 했다. 안드로이드 6부터는 앱에서 필요한 권한이 있을 때 런타임에서 권한을 받게 되었는데, 위치 권한은

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

manifest.xml

ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION 두개의 권한을 받아 사용하였다. 첫번째 줄에 선언한 권한은 네트워크 만을 이용하여 대략적인 위치 정보를 요청하는 권한이고 두번째 줄에 선언한 권한은 GPS와 네트워크를 이용하여 정확한 위치 정보를 요청하는 권한이다. ACCESS_FINE_LOCATION 권한은 반드시 ACCESS_COARSE_LOCATION권한이 허용되어야 한다.

백그라운드에서의 위치 권한은 안드로이드 10 미만으로는 따로 선언하지 않고 사용할 수 있다.

안드로이드 10 이상 부터는 위치 정보 사용이 포그라운드/ 백그라운드로 나누어지게 되는데 백그라운드에서 위치 권한을 사용하려면

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

manifest.xml

ACCESS_BACKGROUND_LOCATION 권한을 따로 요청해야한다.

안드로이드 11이상 부터는 백그라운드에서 위치정보 사용 시 런타임 권한을 두 번 요청 해야한다.

포그라운드/ 백그라운드 위치 권한을 동시에 받으면 제대로 권한 체크가 되지 않고 무시하게 된다.

백그라운드 위치 권한은 왜 백그라운드 위치 권한을 받는지에 대한 다이얼로그와 함께 백그라운드 위치 권한을 사용자가 허용으로 설정할 수 있도록 설정 페이지로 보내주어야 하며 사용자 선택에 따른 결과 처리도 알맞게 따로 해주어야 한다.

3-2) 좌표 값 얻기

나는 Google Play 서비스 Location API를 사용하여 일정한 시간 주기로 좌표 값을 가져왔다. FusedLocationProviderClient가 제공하는 getCurrentLocation 메서드를 이용하여 현재 위치 값을 가져왔다. 문서에는 getLastLocation메서드를 사용해 좌표값을 가져오는 것을 권장한다고 적혀있지만 이전 프로젝트에서 사용했을 때 위치 설정을 막아 놓았다가 막 켠 상태라면 저장되어 있는 좌표 값이 없어 좌표가 null로 반환되는 오류가 있었다. 이 때문에 지속적으로 위치 정보를 가져오지 않고 한번만 가져와도 무방하다면 getCurrentLocation 메서드를 사용하는게 더 좋다고 생각한다.

계속해서 업데이트 되는 좌표를 얻기 위해서는 requestLocationUpdates 메서드를 사용하여 지속적으로 위치를 업데이트 할 수 있다. 위치 정보를 가져올 시간 간격, 정확도를 설정해 LocationRequest객체를 생성하여 설정한 간격으로 좌표값을 가져온다.

 

환경 변수 란 ?

어떠한 프로세스가 실행될 때 영향을 미치는 동적인 값

OS 단에 선언되어있는 배포 환경에 따라 값을 동적으로 변경하기 위한 변수

내가 하고 싶었던 건?

환경변수인 NODE_ENV 값을 local, development, production으로 각각 나누어 의도한 바에 따라 .env파일을 분기하여 사용하고 싶었다

시행착오

  1. process.env 환경변수를 vue.파일의 script 태그 안에서 콘솔로 로그를 찍으니 계속 undefined를 얻었다.. → client side에서는 process.env를 사용 할 수 없다는 사실을 모르고 있었다. 알고보니 process.env는 server side에서만 사용이 가능했고, clientside에서도 활용이 가능하게 하기 위해 nuxt에서는 nuxt.config.ts 파일 안에 config 설정을 따로 두고 있었다.
  2. cmd 에서 환경변수를 set하는 방법을 몰라서 한참을 헤맸다. package.json 의 scripts 부분 안에 있는 명령어들을 지정할 때이런 방식으로 환경변수를 셋팅 하니 잘 동작했다.
  3. SET 환경변수명=값 & nuxt build
  4. SET NODE_ENV를 변경해도 저절로 .env 파일을 찾아가지 못했다. 예전 Vue CLI를 이용해서 프로젝트를 생성했을때는 뒤에 --mode local 이런 방식으로 지정하면 .env.local 파일의 변수값을 바라보게 자동으로 셋팅이 되었는데, Nuxt3는 그렇게 되지 않았고const phase = process.env.PHASE!!주의할 점!!따라서 nuxt.config.ts 안에서 process.env값을 runtimeConfig로 정의하여 컴포넌트 안에서 사용해야한다.
  5. nuxt에서는 process.env 값을 component안에서 console로 찍어 확인하면 nuxt.config.ts 안에서의 process.env 값과 다르다.
  6. require(’dotenv’).config({path:./.env${phase}})
  7. 해결방법으로 스크립트에 PHASE라는 환경변수값을 셋팅하고, 그값을 찾아와 nuxt.config.ts 파일 에서 dotenv의 파일 path값을 동적으로 바꿔주었다. ex) SET PHASE=local&&nuxi dev

CSR (Client-side only rendering)

과정

  1. 브라우저가 빈 HTML Document를 다운받는다
  2. 브라우저가 JS를 다운받고 실행시킨다
  3. 앱이 렌더링 되고 interactive 해진다

장점

  1. 서버와의 호환을 생각할 필요가 없기 때문에 개발 속도가 빠르다.
  2. SSR은 자바스크립트를 지원하는 플랫폼에서 서버를 가동하는데 비용이 들고, CSR은 정적인 HTML, CSS, JS파일들만 호스팅 하는 서버만 필요하기 때문에 상대적으로 비용이 저렴하다.
  3. 전체적으로 코드가 브라우저에서 실행되기 때문에 인터넷이 가능하지 않은 환경에서도 계속 실행이 가능하다.

단점

  1. 브라우저가 다운로드, 파싱, 자바스크립트를 실행 시킬 때까지 기다려야 하기 때문에, 다운로드하는 네트워크와 사용자의 디바이스에 따라 시간이 오래 걸릴 수 있음
  2. search engine이 첫시도에 페이지가 완전히 렌더링 될 때까지 기다릴 수 없기 때문에 SEO가 어렵다.

Universal Rendering

브라우저가 URL을 요청하면 서버는 완전히 렌더링 된 HTML페이지를 브라우저에게 보낸다. 페이지가 미리 생성되었던 캐싱되었던 Nuxt는 서버 환경에서 자바스크립트를 실행 시키고 HTML을 만들어낸다. CSR과 다르게 사용자는 애플리케이션의 내용을 즉각적으로 볼 수 있다. 이 단계는 정통적인 server-side rendering 방식과 비슷하다

CSR의 장점도 함께 가져가기 위해 HTML document가 다운로드 되었을 때 Client는 자바스크립트 코드를 백그라운드에서 실행 시킨다. 브라우저는 코드를 다시 해석하고 vue.js는 애플리케이션을 interactive하게 만든다.

정적 페이지를 브라우저에서 interactive하게 만드는 것을 Hydration이라고 한다.

Universal rendering은 nuxt 애플리케이션이 빠르게 페이지를 다운로드하는 동시에 CSR의 장점도 함께 보존한다. 게다가 애플리케이션의 content가 HTML document에 이미 존재한 채로 브라우저에 전달 되기 때문에 크롤러가 overhead없이 그것을 스캔할 수 있다.

과정

  1. 완전한 HTML이 브라우저로 전달되고 렌더링 된다
  2. 브라우저가 백그라운드에서 자바스크립트를 다운로드하고 실행 시킨다
  3. Hydration step이 완전히 끝나고 애플리케이션이 완전히 interactive해진다

장점

  1. 사용자가 바로 페이지에 접근이 가능하기 때문에 브라우저가 정적 컨텐츠를 더 빨리 보여줄 수 있다.
  2. Universal rendering은 완전한 HTML을 전달 하기 때문에 웹 크롤러가 페이지의 내용을 바로 찾을 수 있기 때문에 SEO가 가능하다.

단점

  1. 서버와 브라우저 환경이 같은 API를 제공하지 않거나, 서버와 브라우저 간 이질감 없이 실행되기 위해 코드를 짜는것이 까다로울 수 있다.
  2. 화면 렌더링을 위해 서버는 가동 되어야 한다. 이는 전통적인 서버와 같이 비용이 들어간다. 그러나 client side rendering navigation을 통해 비용이 많이 감소된다.

features

Assets

  • public : server root에서 파일 그대로 브라우저로 전달함.
  • assets : build tool에 의해 처리가 됨

Vite나 Webpack의 주기능은 자바스크리트 파일들을 처리하는 것임. 그러나 plugins(Vite)나 loaders(Webpack) 을 통해 다른 형태의 assets들도 처리 함( ex) stylesheets, fonts, SVG 등)

이 작업은 원래의 파일을 주로 성능이나 캐싱을 위한 절차 임( style파일을 최소화, 브라우저 캐시무효화)

Head Management

useHead Composable

ex)

<script setup>
useHead({
  titleTemplate: 'My App - %s',
  // or, instead:
  // titleTemplate: (title) => `My App - ${title}`,
  viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
  charset: 'utf-8',
  meta: [
    { name: 'description', content: 'My amazing site.' }
  ],
  bodyAttrs: {
    class: 'test'
  }
})
</script>

setup function 안에서 useHead메서드를 사용해서 meta속성들(title, titleTemplate, base, script, style, meta, link, htmlAttrs, bodyAttrs)을 커스터마이징 해 사용할 수 있음. charset, viewport속성은 예시와 같이 chartset, viewport key값에 value를 설정해 쉽게 커스텀 할 수 있음

Meta Components

Nuxt는 metadata를 vue component template에서 사용할 수 있게 <Title>, <Base>, <Script>, <Style>, <Meta>, <Link>, <Body>, <Html>and <Head> 컴포넌트를 제공함

<script setup>
const title = ref('Hello World')
</script>
<template>
	<div>
		<Head>
			<Title>{{ title }}</Title>
			<Meta name="description" :content="title" />
			<Style type="text/css" children="body { background-color: green; }" />
		</Head>
		<h1>{{ title }}</h1>
	</div>
</template>

usage with definePageMeta

현재의 route의 metadata를 설정하기 위해 useHead 메서드와 definePageMeta메서드는 함께 사용가능함

<script setup>
definePageMeta({
  title: 'Some Page'
})
</script>

위의 예시는 page에 해당하는 vue파일임

<script setup>
const route = useRoute()

useHead({
  meta: [{ name: 'og:title', content: `App Name - ${route.meta.title}` }]
})
</script>

위의 예시는 layout파일에 해당함.

각각 page에 해당하는 파일에 meta 속성을 설정 해놓고, layout 파일에서 route의 현재 route의 메타데이터를이용해 html 데이터를 설정 할 수 있음.

Data Fetching

Nuxt는 데이터를 핸들링 하기 위해 useFetch, useLazyFetch, useAsyncData, useLazyAsyncData, 를 제공함 ⇒ 이 메서드들은 setup이나, lifecycle hook에서만 사용 가능함.****

useAsyncData

page, component, plugin에서 비동기적으로 데이터를 받을 때 useAsyncData를 사용할 수 있음

ex)

let counter = 0
export default () => {
  counter++
  return JSON.stringify(counter)
}

위의 예시는 server/api/count.ts 파일

<script setup>
const { data } = await useAsyncData('count', () => $fetch('/api/count'))
</script><template>
  Page visits: {{ data }}
</template>

count의 data를 받아 올때는 위와 같이 사용함

useLazyAsyncData

useAsyncData에 lazy:true 옵션을 추가한 것과 동일함.

async function 이 페이지 전환을 막지않음. data가 null일때도 data를 핸들링 할 수 있음

<template>
	<div>
    {{ pending ? 'Loading' : count }}
  </div>
</template>
<script setup>
const { pending, data: count } = useLazyAsyncData('count', () => $fetch('/api/count'))
watch(count, (newCount) => {
  // Because count starts out null, you won't have access
  // to its contents immediately, but you can watch it.
})
</script>

useFetch

page, component, plugin 파일 안에서 useFetch 메서드를 사용 할 수 있음

useFetch는 어느 URL이든 접근하여 데이터를 fetch 할 수 있게 하는 메서드임.

이 메서드는 useAsyncData와 $fetch를 함께 포함하고 있는 개념이기 때문에 사용하기 편리함.

<script setup>
const { data } = await useFetch('/api/count')
</script><template>
  Page visits: {{ data.count }}
</template>

useLazyFetch

useFetch에 lazy:true 옵션을 추가한 것과 동일함.

async function이 페이지 전환을 막지 않음

data가 null일때도 핸들링이 가능함.

<template><!-- you'll need to handle a loading state -->
  <div v-if="pending">
    Loading ...
  </div>
	<div v-else>
		<div v-for="post in posts">
			<!-- do something -->
    </div>
	</div>
</template>
<script setup>
const { pending, data: posts } = useLazyFetch('/api/posts')
watch(posts, (newPosts) => {
  // Because posts starts out null, you won't have access
  // to its contents immediately, but you can watch it.
})
</script>

Refreshing Data

data 요청을 다시 보내 data를 새로 load해야 하는 겨우. refresh API를 사용할 수 있음.

useAsyncData 를 이용해 refresh 메서드를 받아 사용함

<script setup>
const page = ref(1);

const { data:users, loading, refresh, error } = await useFetch(() => `users?page=${page.value}&take=6`, { baseURL: config.API_BASE_URL }
);

function previous(){
  page.value--;
  refresh();
}

function next() {
  page.value++;
  refresh();
}
</script>

refreshNuxtData

useAsyncData, useLazyAsyncData, useFetch, useLazyFetch 의 캐시를 무효화 하고, refetch하기 위한 메서드.

현재 페이지의 모든 data를 새롭게 refresh 할때 사용

<template><div>
    {{ pending ? 'Loading' : count }}
  </div><button @click="refresh">Refresh</button></template><script setup>
const { pending, data: count } = useLazyAsyncData('count', () => $fetch('/api/count'))

const refresh = () => refreshNuxtData('count')
</script>

Isomorphic fetch and $fetch

브라우저에서 fetch를 사용할때, user의 헤더(ex)cookie)는 바로 API로 보내진다. 그러나 server-side-rendering에서 fetch 요청은 서버 안에서 발생하게 되기 때문에 이는 브라우저의 cookie를 전송 받지 못하고, 응답에도 cookie를 보낼 수 없다.

Pass client headers to the API

이 문제는 useRequestHeaders를 사용해서 server-side의 API에서 cookie를 접근할 수 있음

<script setup>
const { data } = useFetch('/api/me', {
  headers: useRequestHeaders(['cookie'])
})
</script>

위는 브라우저에서 cookie를 받아 사용하는 예시

Pass on cookies from server-side API calls on SSR response

만약 cookie를 반대로 응답에 포함하여 전달하고 싶을 때는 아래 예시와 같이 사용

export const fetchWithCookie = async (url: string, cookieName: string) => {
  const response = await $fetch.raw(url)
  if (process.server) {
    const cookies = Object.fromEntries(
      response.headers.get('set-cookie')?.split(',').map((a) => a.split('='))
    )
    if (cookieName in cookies) {
      useCookie(cookieName).value = cookies[cookieName]
    }
  }
  return response._data
}
<script setup lang="ts">
// This composable will automatically pass on a cookie of our choice.
const result = await fetchWithCookie("/api/with-cookie", "test")
onMounted(() => console.log(document.cookie))
</script>

Using async setup

async setup() composable에는 처음 await를 사용하고 나서 현재 component의 instance가 손실됨.

따라서 useFetch같은 메서드를 여러번 호출 하기 위해서는 <script setup>을 사용하거나 함께 묶어서 await를 걸어야 함

State Management

useState

SSR-frendly한 반응형을 만들기 위해 Nuxt는 useState라는 composable을 제공함.

이 메서드를 이용해 만든 value는 server-side 렌더링 이후에도 보존됨.

모든 컴포넌트들에서 공유됨.

Basic usage

<script setup>
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>
<template>
	<div>
    Counter: {{ counter }}
    <button @click="counter++">
      +
    </button><button @click="counter--">
      -
    </button>
	</div>
</template>

위 예시는 component-local counter state를 사용하고 있음. 하지만 다른 컴포넌트들 역시 useState(’counter’)로 같은 반응형 state를 공유한다.

Error Handling

Nuxt3는 full-stack framework로 예측불허한 다양한 런타임 에러들이 다양한 context에서 발생할 수 있음.

  • Errors during the Vue rendering lifecycle(SSR + SPA)
  • Errors during API or Nitro server lifecycle
  • Server and client startup errors(SSR + SPA)

Errors during the Vue rendering lifecycle (SSR + SPA)

Vue 에러를 onErrorCaptured 에서 핸들링 할수 있음

또한 Nuxt는 에러가 발생하면 상위 레벨로 에러를 전파 시켜 vue:error 훅이 호출 됨

vueApp.config.errorHandler에서 핸들링 가능한 모든 Vue에러를 받을 수 있음

Server and client startup errors (SSR + SPA)

Nuxt는 Nuxt 애플리케이션이 시작할 때 에러가 발생하면 app:error 훅을 호출한다.

이는

  • plugin을 실행할 때
  • app:created, app:beforeMount 훅을 처리할 때
  • app이 mount될때의 에러는 onErrorCaptured 나 vue:error 훅에서 처리해야함
  • app:mounted 훅을 처리할 때

호출된다.

Errors during API or Nitro server lifecycle

server-side에서 handler를 지정 할 수는 없지만, 에러 페이지를 띄울 수 있음.

Rendering an error page

Nuxt가 렌더링 시 client side나 server side lifecycle에서 에러를 만나면 JSON 응답 또는 HTML 에러 페이지를 렌더링 함(요청 헤더에 Accept:application/json 이 있다면)

이 에러 페이지는 app.vue와 같은 레벨의 source directory의 ~/error.vue로 만들어서 커스터마이징 할 수 있다. 만약 error 페이지를 지우고 싶다면 clearError helper 함수로 다른 페이지로 에러 발생시 리다이렉트 시킬 수 있다.

<template>
	<button @click="handleError">Clear errors</button>
</template>
<script setup>
const props = defineProps({
  error: Object
})

const handleError = () => clearError({ redirect: '/' })
</script>

Error helper methods

  • useError

→ 전역으로 발생하는 Nuxt 에러를 핸들링 할 수 있음

  • throwError

→ 이 함수는 client side, server side에서 middleware, plugin, setup함수에서 언제든지 사용 가능함. 이 함수는 트리거가 될때마다 에러페이지를 반환함. 이 에러페이지는 clearError 함수로 clear시킬 수 있음

  • clearError

→ 이 함수는 핸들링 된 Nuxt error를 clear하며 다른 페이지로 redirect 할 수 있음

Rendering errors within your app

<NuxtErrorBoundary> 컴포넌트는 다른 에러 페이지를 생성하지 않고도 에러페이지를 렌더링 하게 해줌.

이 컴포넌트는 반응형으로 에러를 핸들링 할 수 있음.

<template><!-- some content -->
  <NuxtErrorBoundary @error="someErrorLogger">
<!-- You use the default slot to render your content -->
    <template #error="{ error }">
      You can display the error locally here.
      <button @click="error = null">
        This will clear the error.
      </button>
		</template>
	</NuxtErrorBoundary>
</template>

Server Routes

Nuxt는 자동으로 /server/api, /server/routes, /server/middleware 디렉토리에 들어있는 파일들을 스캔하고 API와 서버 핸들러에 등록함

이 핸들러는 JSON 데이터를 리턴하며, Promise, event.res.end() 를 사용하여 응답을 보낸다.

ex) server/api/hello.ts 파일을

export default defineEventHandler((event) => {
  return {
    api: 'works'
  }
})

전역적으로 await $fetch(’/api/hello’)로 API를 호출 할 수 있음

Server Routes

~/server/api 안의 파일들은 prefix로 /api라는 path를 자동으로 갖게 되며, /api라는 prefix가 붙지 않고 사용하려면 ~/server/routes 디렉토리에 추가 해야함.

예를들어 ~/server/routes/hello.ts 라는 파일을 아래와 같이 작성하면

export default defineEventHandler(() => 'Hello World!')

http://localhost:3000/hello 로 접근 할 수 있음

Server Middleware

Nuxt는 ~/server/middleware 디렉토리 안에 있는 모든 파일들을 자동으로 읽고 server middleware를 생성함.

middleware handler는 모든 요청에 server의 route를 거치기 전 헤더를 체크하거나 로직을 추가 하는데 사용 함.

ex) /server/middleware/log.ts

export default defineEventHandler((event) => {
  console.log('New request: ' + event.req.url)
})

ex) /server/middleware/auth.ts

export default defineEventHandler((event) => {
  event.context.auth = { user: 123 }
})

Matching Route Parameters

server routes는 동적으로 파라미터를 가져올 수 있다.

예를들어 /api/hello/[name].ts 파일의 이름을 event.context.params.name으로 찾아올 수 있음

ex) /server/api/hello/[name].ts

export default defineEventHandler(event => Hello, ${event.context.params.name}!)

await $fetch(’/api/hello/nuxt’)를 호출하면 Hello, nuxt!를 얻을 수 있음

Matching HTTP Method

파일 이름 뒤에 HTTP Method(.get, .post, .put, .delete ...)를 붙여서 핸들링 할 수 있음.

ex) /server/api/test.get.ts

export default defineEventHandler(() => 'Test get handler')

ex) /server/api/test.post.ts

export default defineEventHandler(() => 'Test post handler')

위의 예시들은 각각 get 핸들러 , post 핸들러를 리턴함

Catch-all route

Catch-all route는 루트 path 이후에 catch-all route가 정해져 있지 않으면 해당 path를 포함하는 모든 요청에 대한 핸들러를 작성 할 수 있음

ex) /server/api/foo/[...].ts

export default defineEventHandler(() => Default foo handler)

ex) /server/api/[...].ts

export default defineEventHandler(() => Default api handler)

Handling Requests with Body

ex) /server/api/submit.post.ts

export default defineEventHandler(async (event) => {
    const body = await useBody(event)
    return { body }
})

예시에서 $fetch(’/api/submit’, {method: ‘post’, body :{test:123}})로 호출 할 수 있음

.post.ts 파일은 useBody를 사용할 수 있음, 만약 .get.ts 파일을 핸들링 하려고 useBody를 쓰면 405 에러가 남

Handling Requests with query parameters

ex) /server/api/query.get.ts

export default defineEventHandler((event) => {
  const query = useQuery(event)
  return { a: query.param1, b: query.param2 }
})

Get access to the runtimeConfig

ex) /server/api/foo.ts

export default defineEventHandler((event) => {
  const config = useRuntimeConfig()
  return { key: config.KEY }
})

Access Request Cookies

ex)

export default defineEventHandler((event) => {
  const cookies = useCookies(event)
  return { cookies }
})

Runtime Config

Nuxt는 런타임에 config를 컨트롤 할 수 있는 config API를 제공함.

Exposing runtime config

nuxt.config파일의 runtimeConfig 옵션에 런타임 설정을 할 수 있음

ex) nuxt.config.ts

export default defineNuxtConfig({
  runtimeConfig: {
    // The private keys which are only available within server-side
    apiSecret: '123',
    // Keys within public, will be also exposed to the client-side
    public: {
      apiBase: '/api'
    }
  }
})

Environment Variables

Nuxt CLI 는 built-in 으로 dotenv를 지원함

만약 .env 파일이 프로젝트 root 디렉토리에 존재하면 process.env에 자동으로 load 되고 module과 nuxt.config에서 접근 가능함

ex) .env

NUXT_API_SECRET=api_secret_token
NUXT_PUBLIC_API_BASE=https://nuxtjs.org

ex) nuxt.config.ts

export default defineNuxtConfig({
  runtimeConfig: {
    apiSecret: '',
    public: {
      apiBase: '', // Or a default value
    }
  },
})

Accessing runtime config

  • Vue app

런타임 config에 접근 하기 위해 useRuntimeConfig() 를 호출함

ex)

<template><div><div>Check developer console!</div></div></template><script setup>
const config = useRuntimeConfig()
console.log('Runtime config:', config)
if (process.server) {
  console.log('API secret:', config.apiSecret)
}
</script>

client side에서는 config.pubic만 접근 가능하며 읽기/쓰기가 가능함

server side에서는 모든 key에 접근 가능하지만, 쓰기가 불가능함

useRentimeConfig()는 setup이나 Lifecycle hook에서만 사용가능함

  • Plugins

plugin에서 useRuntimeConfig를 사용하려면 defineNuxtPlugin 안에서 사용함

ex)

export default defineNuxtPlugin((nuxtApp) => {
  const config = useRuntimeConfig()
  console.log('API base URL:', config.public.apiBase)
});
  • Server Routes

ex)

export default async () => {
  const result = await $fetch('<https://my.api.com/test>', {
    headers: {
      Authorization: `Bearer ${useRuntimeConfig().apiSecret}`}
  })
  return result
}

Teleports

Vue3는 <Teleport> 컴포넌트를 제공함. 이는 Vue application 이 아닌 DOM의 어느곳에서든지 렌더링이 됨

Nuxt는 SSR에서 teleport는 body만 가능함, client-side에서 사용은 <ClientOnly> warpper로 감싸야 함

  • body teleport

ex)

<template>
	<button @click="open = true">
    Open Modal
  </button>
	<Teleport to="body">
		<div v-if="open" class="modal">
			<p>Hello from the modal!</p>
			<button @click="open = false">
        Close
      </button>
		</div>
	</Teleport>
</template>
  • client-side teleport

ex)

	<ClientOnly>
		<Teleport to="#some-selector">
				<!-- content -->
	  </Teleport>
	</ClientOnly>
</template>

+ Recent posts