Haskell로 함수형 웹 서버 만들기: Yesod 프레임워크 활용 🚀
안녕하세요, 코딩 마니아 여러분! 오늘은 정말 흥미진진한 주제로 여러분과 함께할 거예요. 바로 Haskell이라는 멋진 함수형 언어로 웹 서버를 만드는 방법에 대해 알아볼 거거든요. 그것도 Yesod라는 초강력 프레임워크를 활용해서 말이죠! 😎
여러분, 혹시 "함수형 프로그래밍"이라는 말만 들어도 머리가 아프진 않나요? ㅋㅋㅋ 걱정 마세요! 오늘 제가 여러분의 두뇌를 시원하게 해드릴게요. 함수형 프로그래밍의 매력에 푹 빠져보시죠!
🎓 알쓸신잡: Haskell은 순수 함수형 프로그래밍 언어로, 1990년에 처음 등장했어요. 수학적 개념을 바탕으로 만들어져서 코드의 안정성과 예측 가능성이 뛰어나답니다. 마치 수학 공식처럼 깔끔하고 우아한 코드를 작성할 수 있죠!
자, 이제 본격적으로 Haskell과 Yesod의 세계로 들어가볼까요? 준비되셨나요? 그럼 출발~! 🏁
1. Haskell, 너 도대체 뭐니? 🤔
Haskell이라고 하면 뭐가 제일 먼저 떠오르시나요? 혹시 "어려워 보이는 언어"? "수학 천재들이나 쓰는 언어"? ㅋㅋㅋ 맞아요, 처음엔 저도 그렇게 생각했어요. 하지만 알고 보면 Haskell은 정말 매력적인 언어랍니다!
Haskell은 순수 함수형 프로그래밍 언어예요.
이게 무슨 말이냐고요? 쉽게 말해서, 모든 것을 함수로 표현한다는 거죠. 변수를 변경하거나 상태를 바꾸는 대신, 입력값을 받아 결과를 반환하는 함수들의 조합으로 프로그램을 만들어요.💡 Tip: Haskell을 배우면 다른 프로그래밍 패러다임을 이해하는 데도 큰 도움이 돼요. 마치 새로운 언어를 배우면 모국어를 더 잘 이해하게 되는 것처럼 말이죠!
Haskell의 특징을 좀 더 자세히 알아볼까요?
- 정적 타입 시스템: 컴파일 시점에 타입 오류를 잡아내서 런타임 에러를 줄여줘요. 마치 코드에 방탄조끼를 입히는 것과 같죠! 💪
- 게으른 평가(Lazy Evaluation): 필요할 때만 계산을 수행해서 효율적이에요. 게으른 거 아니에요, 똑똑한 거예요! 🧠
- 순수성(Purity): 부작용 없는 함수들로 이루어져 있어 예측 가능하고 테스트하기 쉬워요. 순수한 마음처럼 깨끗한 코드! ✨
- 강력한 타입 추론: 복잡한 타입도 알아서 추론해주니 코드가 간결해져요. 타입 쓰는 게 귀찮았던 분들 주목! 👀
이런 특징들 때문에 Haskell은 특히 복잡한 시스템을 설계하거나 병렬 프로그래밍을 할 때 빛을 발한답니다. 금융 업계나 학계에서 많이 사용되는 이유가 바로 이 때문이에요.
그런데 말이죠, Haskell을 배우면서 느낀 건... 이 언어가 마치 퍼즐 게임 같다는 거예요! 처음엔 어렵게 느껴지지만, 하나씩 맞춰가다 보면 어느새 큰 그림이 완성되는 그 느낌, 아시죠? 😉
자, 이제 Haskell에 대해 조금은 친근해지셨나요? ㅎㅎ 그럼 이제 본격적으로 Yesod 프레임워크를 알아볼 차례예요. Haskell로 웹 개발을 한다니, 얼마나 신나는 일인가요?! 🎉
그런데 잠깐, 여러분! 혹시 이런 생각 들지 않나요? "아, 이거 배우면 나중에 어디다 써먹지?" 걱정 마세요! 요즘엔 다양한 분야에서 프로그래밍 실력을 필요로 하고 있어요. 예를 들어, 재능넷같은 재능 공유 플랫폼에서도 프로그래밍 강의나 코딩 과외 같은 서비스가 인기 많답니다. 여러분이 Haskell 전문가가 되면, 그것도 하나의 멋진 재능이 되는 거죠! 😎
자, 이제 Yesod의 세계로 들어가볼까요? 준비되셨나요? 그럼 고고씽~! 🚀
2. Yesod, 웹 개발의 새로운 패러다임 🌈
자, 이제 Yesod에 대해 알아볼 차례예요. Yesod(예소드)라고 하면 뭐가 떠오르시나요? 혹시 '예수님'? ㅋㅋㅋ 아니에요, 전혀 다른 거예요! Yesod는 히브리어로 '기초'라는 뜻을 가진 단어랍니다. 그만큼 탄탄한 기초를 제공하는 웹 프레임워크라는 뜻이죠!
Yesod는 Haskell로 작성된 오픈 소스 웹 프레임워크예요.
다른 언어의 유명한 프레임워크들 - 예를 들면 Ruby on Rails나 Django 같은 - 과 비슷한 역할을 한다고 보면 돼요. 하지만 Yesod는 Haskell의 강력한 기능들을 최대한 활용해서 더욱 안전하고 효율적인 웹 애플리케이션을 만들 수 있게 해줘요.🌟 재미있는 사실: Yesod의 창시자인 Michael Snoyman은 처음에 자신의 웹사이트를 만들기 위해 이 프레임워크를 개발했대요. 그러다 보니 점점 커져서 지금의 Yesod가 되었답니다. 작은 아이디어가 큰 프로젝트로 발전한 멋진 사례죠!
Yesod의 주요 특징들을 살펴볼까요?
- 타입 안전성: Haskell의 강력한 타입 시스템을 활용해 런타임 에러를 최소화해요. 마치 코드에 방탄복을 입히는 것과 같죠! 💪
- 성능: 컴파일된 코드를 실행하기 때문에 매우 빠른 성능을 자랑해요. 번개처럼 빠른 웹사이트를 만들 수 있어요! ⚡
- 모듈화: 재사용 가능한 위젯과 핸들러를 쉽게 만들 수 있어요. 레고 블록처럼 조립해서 사용할 수 있답니다! 🧱
- RESTful 라우팅: 깔끔하고 직관적인 URL 구조를 쉽게 만들 수 있어요. 사용자 친화적인 URL, 좋죠? 😊
- 템플릿 언어: Hamlet, Cassius, Lucius 등의 템플릿 언어를 제공해 HTML, CSS, JavaScript를 효율적으로 생성할 수 있어요. 마법사처럼 코드를 주문할 수 있답니다! 🧙♂️
이런 특징들 때문에 Yesod는 특히 대규모 웹 애플리케이션을 개발할 때 진가를 발휘해요. 복잡한 비즈니스 로직을 안전하게 구현하면서도 높은 성능을 유지할 수 있거든요.
그런데 말이에요, Yesod를 배우면서 느낀 건... 이 프레임워크가 마치 고급 요리 도구 세트 같다는 거예요! 처음엔 사용법이 복잡해 보이지만, 익숙해지면 정말 멋진 요리(웹 애플리케이션)를 만들 수 있어요. 여러분도 Yesod 마스터 셰프가 되어보는 건 어떨까요? 👨🍳👩🍳
자, 이제 Yesod에 대해 조금은 이해가 되셨나요? ㅎㅎ 정말 멋진 프레임워크죠? 이제 우리가 할 일은 이 멋진 도구를 이용해서 실제로 웹 서버를 만들어보는 거예요!
그런데 잠깐, 여러분! 혹시 이런 생각 들지 않나요? "와, 이거 배우면 나중에 대박 날 수 있겠는데?" 맞아요, 여러분의 직감이 정확해요! 요즘 IT 업계에서는 새로운 기술을 습득한 개발자들의 가치가 정말 높아지고 있어요. 재능넷 같은 플랫폼에서도 Haskell이나 Yesod 관련 강의나 프로젝트 의뢰가 늘어나고 있다고 해요. 여러분이 이 기술을 마스터하면, 정말 귀한 인재가 되는 거죠! 💎
자, 이제 실제로 Yesod로 웹 서버를 만들어볼 준비가 되셨나요? 그럼 다음 단계로 넘어가볼까요? 레츠고~! 🏃♂️🏃♀️
3. Yesod로 첫 웹 서버 만들기: Hello, World! 👋
자, 이제 정말 신나는 시간이 왔어요! 우리가 배운 Haskell과 Yesod를 이용해서 실제로 웹 서버를 만들어볼 거예요. 긴장되나요? 걱정 마세요, 천천히 함께 해볼게요! 😊
우리의 첫 번째 미션은 바로 전통의 "Hello, World!" 웹 페이지를 만드는 거예요.
프로그래밍계의 성년식 같은 거죠! ㅋㅋㅋ먼저, Yesod를 설치해야 해요. Haskell의 패키지 매니저인 Cabal을 이용해서 설치할 수 있어요.
cabal install yesod
설치가 완료되면, 이제 우리의 첫 Yesod 프로젝트를 만들 차례예요!
stack new my-first-yesod-project yesod-simple
이 명령어를 실행하면, Yesod가 기본적인 프로젝트 구조를 만들어줘요. 마치 케이크의 기본 시트를 준비해주는 것과 같죠! 🍰
자, 이제 프로젝트 폴더로 이동해볼까요?
cd my-first-yesod-project
폴더 안에 들어가보면, 여러 파일들이 생성되어 있을 거예요. 그 중에서 우리가 주목해야 할 파일은 src/Application.hs
예요. 이 파일이 우리 애플리케이션의 심장이라고 할 수 있죠!
이제 이 파일을 열어서 다음과 같이 수정해볼게요:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
data HelloWorld = HelloWorld
instance Yesod HelloWorld
mkYesod "HelloWorld" [parseRoutes|
/ HomeR GET
|]
getHomeR :: Handler Html
getHomeR = defaultLayout [whamlet|
<h1>Hello, World!
</h1><p>Welcome to Yesod!
|]
main :: IO ()
main = warp 3000 HelloWorld
</p>
우와, 코드가 좀 복잡해 보이죠? 걱정 마세요, 하나씩 설명해드릴게요! 😉
{-# LANGUAGE ... #-}
: 이건 Haskell의 언어 확장을 활성화하는 부분이에요. Yesod가 제대로 작동하려면 이런 확장들이 필요해요.data HelloWorld = HelloWorld
: 우리 애플리케이션의 기본 데이터 타입을 정의하는 부분이에요.instance Yesod HelloWorld
: HelloWorld를 Yesod의 인스턴스로 만들어줘요.mkYesod "HelloWorld" [parseRoutes| ... |]
: 이 부분이 라우팅을 정의해요. 여기서는 루트 경로(/)를 HomeR이라는 이름의 GET 요청과 연결했어요.getHomeR
: 이 함수가 실제로 "Hello, World!" 페이지를 생성해요.main
: 서버를 3000번 포트에서 실행하도록 설정해요.
자, 이제 이 코드를 저장하고 터미널로 돌아가볼까요? 다음 명령어로 우리의 첫 Yesod 애플리케이션을 실행할 수 있어요!
stack build && stack exec my-first-yesod-project
짜잔! 🎉 이제 브라우저를 열고 http://localhost:3000
으로 접속해보세요. "Hello, World!"가 보이시나요? 축하드려요! 여러분의 첫 Yesod 웹 서버가 성공적으로 실행됐어요!
어때요? 생각보다 어렵지 않죠? 물론 이건 아주 기본적인 예제에요. 하지만 이걸 기반으로 점점 더 복잡하고 멋진 웹 애플리케이션을 만들어갈 수 있어요!
그런데 말이에요, 여러분! 혹시 이런 생각 들지 않나요? "와, 이거 배우면 나중에 멋진 웹사이트도 만들 수 있겠는데?" 맞아요, 정확해요! 실제로 많은 기업들이 안정성과 성능이 중요한 웹 서비스에 Haskell과 Yesod를 사용하고 있어요. 재능넷 같은 플랫폼에서도 이런 기술을 활용한 프로젝트 의뢰가 늘어나고 있다고 해요. 여러분이 이 기술을 마스터하면, 정말 멋진 웹 개발자가 될 수 있을 거예요! 💻✨
자, 이제 우리의 첫 Yesod 웹 서버를 만들어봤어요. 어떠셨나요? 재미있었죠? 다음 단계에서는 조금 더 복잡한 기능을 추가해볼 거예요. 준비되셨나요? 그럼 고고! 🚀
4. Yesod의 강력한 기능: 폼 처리하기 📝
자, 이제 우리의 "Hello, World!" 웹 서버를 조금 더 발전시켜볼 거예요. 이번에는 사용자 입력을 받을 수 있는 폼을 만들어볼게요. 폼이라... 뭔가 어려워 보이죠? 하지만 Yesod를 사용하면 정말 쉽답니다! 😎
Yesod의 폼 처리 기능은 정말 강력해요. 타입 안전성을 보장하면서도 사용하기 쉽게 만들어져 있죠.
마치 레고 블록을 조립하는 것처럼 쉽게 폼을 만들 수 있어요!먼저, 우리의 Application.hs
파일을 다음과 같이 수정해볼게요:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
import Data.Text (Text)
data HelloWorld = HelloWorld
instance Y esod HelloWorld
mkYesod "HelloWorld" [parseRoutes|
/ HomeR GET POST
|]
data Person = Person
{ personName :: Text
, personAge :: Int
}
personForm :: Form Person
personForm = renderDivs $ Person
<$> areq textField "Name" Nothing
<*> areq intField "Age" Nothing
getHomeR :: Handler Html
getHomeR = do
(widget, enctype) <- generateFormPost personForm
defaultLayout
[whamlet|
<h1>Welcome to Yesod!
</h1><form method="post" action="@%7BHomeR%7D" enctype="#{enctype}">
^{widget}
<button>Submit
|]
postHomeR :: Handler Html
postHomeR = do
((result, widget), enctype) <- runFormPost personForm
case result of
FormSuccess person -> defaultLayout [whamlet|
<p>You submitted:
</p><ul>
<li>Name: #{personName person}
</li><li>Age: #{show $ personAge person}
|]
_ -> defaultLayout
[whamlet|
<p>Invalid input, let's try again.
</p><form method="post" action="@%7BHomeR%7D" enctype="#{enctype}">
^{widget}
<button>Submit
|]
main :: IO ()
main = warp 3000 HelloWorld
</button></form></li></ul></button></form>
우와, 코드가 많이 길어졌죠? 걱정 마세요, 하나씩 설명해드릴게요! 😊
data Person = ...
: 이 부분은 우리가 폼에서 받을 데이터의 구조를 정의해요. 이름(Text)과 나이(Int)를 가진 Person 타입을 만들었어요.personForm
: 이 함수가 실제로 폼을 생성해요.areq
는 '필수 입력 필드'를 만드는 함수예요.getHomeR
: 이 함수는 폼을 포함한 페이지를 렌더링해요.generateFormPost
로 폼 위젯을 생성하고, 이를 페이지에 포함시켜요.postHomeR
: 이 함수는 폼 제출을 처리해요. 제출된 데이터가 유효하면 결과를 보여주고, 그렇지 않으면 다시 폼을 표시해요.
이제 이 코드를 저장하고 서버를 다시 실행해볼까요?
stack build && stack exec my-first-yesod-project
브라우저에서 http://localhost:3000
에 접속해보세요. 이제 이름과 나이를 입력할 수 있는 폼이 보일 거예요!
폼에 데이터를 입력하고 제출해보세요. 입력한 데이터가 화면에 표시될 거예요. 멋지죠? 🎉
이렇게 Yesod를 사용하면 타입 안전한 폼을 쉽게 만들 수 있어요. 사용자 입력 검증도 자동으로 처리되니까 정말 편리하죠!
그런데 말이에요, 여러분! 혹시 이런 생각 들지 않나요? "와, 이거 실제 웹사이트에서도 쓸 수 있겠는데?" 맞아요, 정확해요! 실제로 많은 웹사이트에서 이런 방식으로 사용자 정보를 수집하고 처리해요. 재능넷 같은 플랫폼에서도 사용자 프로필 정보를 입력받을 때 이런 폼을 사용할 수 있겠죠? 여러분이 이 기술을 마스터하면, 정말 실용적인 웹 애플리케이션을 만들 수 있을 거예요! 💼✨
자, 이제 우리는 Yesod로 동적인 웹 페이지를 만들어봤어요. 어떠셨나요? 점점 더 재미있어지고 있죠? 다음 단계에서는 데이터베이스와 연동하는 방법을 알아볼 거예요. 준비되셨나요? 그럼 고고! 🚀
5. Yesod와 데이터베이스의 만남: Persistent 사용하기 💾
자, 이제 우리의 Yesod 애플리케이션을 한 단계 더 발전시켜볼 거예요. 이번에는 데이터베이스와 연동해볼 건데요, 어떠세요? 조금 긴장되나요? 걱정 마세요! Yesod의 Persistent 라이브러리를 사용하면 데이터베이스 작업도 정말 쉬워진답니다! 😎
Persistent는 Yesod에서 제공하는 ORM(Object-Relational Mapping) 라이브러리예요.
이를 사용하면 데이터베이스 작업을 Haskell의 타입 시스템을 활용해 안전하고 효율적으로 수행할 수 있어요.먼저, 우리의 프로젝트에 Persistent를 추가해야 해요. package.yaml
파일의 dependencies 섹션에 다음 라인들을 추가해주세요:
- persistent
- persistent-sqlite
- persistent-template
그리고 Application.hs
파일을 다음과 같이 수정해볼게요:
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
import Database.Persist.Sqlite
import Database.Persist.TH
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name Text
age Int
deriving Show
|]
data App = App ConnectionPool
mkYesod "App" [parseRoutes|
/ HomeR GET POST
|]
instance Yesod App
instance YesodPersist App where
type YesodPersistBackend App = SqlBackend
runDB action = do
App pool <- getYesod
runSqlPool action pool
personForm :: Form Person
personForm = renderDivs $ Person
<$> areq textField "Name" Nothing
<*> areq intField "Age" Nothing
getHomeR :: Handler Html
getHomeR = do
(widget, enctype) <- generateFormPost personForm
persons <- runDB $ selectList [] [Desc PersonId]
defaultLayout
[whamlet|
<h1>Welcome to Yesod!
</h1><form method="post" action="@%7BHomeR%7D" enctype="#{enctype}">
^{widget}
<button>Submit
<h2>Registered Persons
<ul>
$forall Entity _ person <- persons
<li>#{personName person} (#{show $ personAge person} years old)
|]
postHomeR :: Handler Html
postHomeR = do
((result, _), _) <- runFormPost personForm
case result of
FormSuccess person -> do
runDB $ insert person
setMessage "Person added"
redirect HomeR
_ -> do
setMessage "Invalid input, please try again"
redirect HomeR
main :: IO ()
main = runStderrLoggingT $ withSqlitePool "test.db" 10 $ \pool -> liftIO $ do
runResourceT $ flip runSqlPool pool $ do
runMigration migrateAll
warp 3000 $ App pool
</li></ul></h2></button></form>
우와, 코드가 많이 복잡해졌죠? 하지만 걱정 마세요. 하나씩 설명해드릴게요! 😊
share [mkPersist ...]
: 이 부분은 Person 데이터 모델을 정의하고, 이에 대한 데이터베이스 테이블을 자동으로 생성해요.instance YesodPersist App
: 이 부분은 우리 앱에서 Persistent를 사용할 수 있게 해줘요.runDB
: 이 함수를 사용해 데이터베이스 작업을 수행할 수 있어요.getHomeR
: 이제 이 함수에서 데이터베이스에서 모든 Person을 가져와 화면에 표시해요.postHomeR
: 폼 제출 시 새로운 Person을 데이터베이스에 추가해요.main
: 여기서 SQLite 데이터베이스 연결을 설정하고, 필요한 테이블을 생성해요.
이제 이 코드를 저장하고 서버를 다시 실행해볼까요?
stack build && stack exec my-first-yesod-project
브라우저에서 http://localhost:3000
에 접속해보세요. 이제 폼에 데이터를 입력하면, 그 데이터가 데이터베이스에 저장되고 화면에 표시될 거예요!
와우! 이제 우리의 Yesod 애플리케이션이 데이터를 저장하고 불러올 수 있게 되었어요. 정말 대단하지 않나요? 🎉
이렇게 Persistent를 사용하면 데이터베이스 작업을 타입 안전하게 수행할 수 있어요. SQL 인젝션 같은 보안 문제도 걱정할 필요가 없죠!
그런데 말이에요, 여러분! 혹시 이런 생각 들지 않나요? "와, 이거 실제 웹 서비스에서도 쓸 수 있겠는데?" 맞아요, 정확해요! 실제로 많은 웹 서비스들이 이런 방식으로 사용자 데이터를 저장하고 관리해요. 재능넷 같은 플랫폼에서도 사용자 프로필이나 게시글 정보를 이런 식으로 데이터베이스에 저장하고 불러올 수 있겠죠? 여러분이 이 기술을 마스터하면, 정말 실용적이고 안전한 웹 서비스를 만들 수 있을 거예요! 💼🔒
자, 이제 우리는 Yesod로 데이터베이스와 연동된 동적인 웹 애플리케이션을 만들어봤어요. 어떠셨나요? 점점 더 실제 웹 서비스에 가까워지고 있죠? 다음 단계에서는 인증 기능을 추가해볼 거예요. 준비되셨나요? 그럼 고고! 🚀
6. Yesod의 보안 기능: 인증과 권한 관리 🔐
자, 이제 우리의 Yesod 애플리케이션에 보안 기능을 추가해볼 차례예요. 실제 웹 서비스에서 인증과 권한 관리는 정말 중요하죠? Yesod는 이런 보안 기능을 쉽게 구현할 수 있도록 도와줘요. 준비되셨나요? 함께 알아봐요! 🕵️♀️
Yesod의 인증 시스템은 유연하면서도 강력해요. 다양한 인증 방식을 지원하고, 세션 관리도 안전하게 처리해줍니다.
먼저, 우리의 프로젝트에 인증 관련 라이브러리를 추가해야 해요. package.yaml
파일의 dependencies 섹션에 다음 라인을 추가해주세요:
- yesod-auth
그리고 Application.hs
파일을 다음과 같이 수정해볼게요:
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
import Yesod.Auth
import Yesod.Auth.Dummy -- 실제 서비스에서는 다른 인증 방식을 사용해야 해요!
import Database.Persist.Sqlite
import Database.Persist.TH
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name Text
age Int
deriving Show
User
ident Text
password Text Maybe
UniqueUser ident
deriving Typeable
|]
data App = App ConnectionPool
mkYesod "App" [parseRoutes|
/ HomeR GET POST
/auth AuthR Auth getAuth
|]
instance Yesod App where
authRoute _ = Just $ AuthR LoginR
isAuthorized HomeR _ = isAuthenticated
isAuthorized (AuthR _) _ = return Authorized
instance YesodPersist App where
type YesodPersistBackend App = SqlBackend
runDB action = do
App pool <- getYesod
runSqlPool action pool
instance YesodAuth App where
type AuthId App = UserId
authenticate creds = runDB $ do
x <- getBy $ UniqueUser $ credsIdent creds
case x of
Just (Entity uid _) -> return $ Authenticated uid
Nothing -> Authenticated <$> insert User
{ userIdent = credsIdent creds
, userPassword = Nothing
}
authPlugins _ = [authDummy]
instance RenderMessage App FormMessage where
renderMessage _ _ = defaultFormMessage
personForm :: Form Person
personForm = renderDivs $ Person
<$> areq textField "Name" Nothing
<*> areq intField "Age" Nothing
getHomeR :: Handler Html
getHomeR = do
(widget, enctype) <- generateFormPost personForm
persons <- runDB $ selectList [] [Desc PersonId]
defaultLayout $ do
setTitle "Welcome To Yesod!"
[whamlet|
<p>
<a href="@%7BAuthR" loginr>Login
$maybe _ <- maybeAuthId
</a><a href="@%7BAuthR" logoutr>Logout
<h1>Welcome to Yesod!
</h1><form method="post" action="@%7BHomeR%7D" enctype="#{enctype}">
^{widget}
<button>Submit
<h2>Registered Persons
<ul>
$forall Entity _ person <- persons
<li>#{personName person} (#{show $ personAge person} years old)
|]
postHomeR :: Handler Html
postHomeR = do
((result, _), _) <- runFormPost personForm
case result of
FormSuccess person -> do
runDB $ insert person
setMessage "Person added"
redirect HomeR
_ -> do
setMessage "Invalid input, please try again"
redirect HomeR
main :: IO ()
main = runStderrLoggingT $ withSqlitePool "test.db" 10 $ \pool -> liftIO $ do
runResourceT $ flip runSqlPool pool $ do
runMigration migrateAll
warp 3000 $ App pool
</li></ul></h2></button></form></a></p>
우와, 코드가 더 복잡해졌죠? 하지만 걱정 마세요. 하나씩 설명해드릴게요! 😊
User
모델: 사용자 정보를 저장하기 위한 새로운 데이터 모델을 추가했어요.instance Yesod App
: 여기서authRoute
와isAuthorized
를 정의해 인증 로직을 구현했어요.instance YesodAuth App
: 이 부분에서 실제 인증 로직을 구현했어요. 여기서는 간단한 Dummy 인증을 사용했지만, 실제 서비스에서는 더 안전한 방식을 사용해야 해요!getHomeR
: 로그인/로그아웃 링크를 추가했어요.
이제 이 코드를 저장하고 서버를 다시 실행해볼까요?
stack build && stack exec my-first-yesod-project
브라우저에서 http://localhost:3000
에 접속해보세요. 이제 로그인 기능이 추가된 걸 확인할 수 있을 거예요!
와우! 이제 우리의 Yesod 애플리케이션에 인증 기능이 추가되었어요. 사용자는 로그인해야만 Person을 추가할 수 있게 되었죠. 정말 멋지지 않나요? 🎉
이렇게 Yesod의 인증 시스템을 사용하면 안전하고 유연한 인증 로직을 쉽게 구현할 수 있어요. 물론 실제 서비스에서는 더 강력한 인증 방식(예: OAuth, 이메일 인증 등)을 사용해야 하겠지만, 기본 구조는 이와 비슷할 거예요.
그런데 말이에요, 여러분! 혹시 이런 생각 들지 않나요? "와, 이제 진짜 웹 서비스 같아졌는데?" 맞아요, 정확해요! 실제 웹 서비스들도 이런 방식으로 사용자 인증을 구현하고 있어요. 재능넷 같은 플랫폼에서도 사용자 로그인, 회원가입, 권한 관리 등을 이런 식으로 구현하고 있겠죠? 여러분이 이 기술을 마스터하면, 정말 안전하고 신뢰할 수 있는 웹 서비스를 만들 수 있을 거예요! 🔒💻
자, 이제 우리는 Yesod로 데이터베이스 연동, 폼 처리, 그리고 인증까지 구현해봤어요. 어떠셨나요? 이제 정말 실제 웹 서비스에 가까워졌죠? 다음 단계에서는 RESTful API를 만들어볼 거예요. API 서버 개발에 관심 있으신 분들, 기대되지 않나요? 준비되셨나요? 그럼 고고! 🚀
7. Yesod로 RESTful API 만들기: JSON과 함께 춤을! 💃
자, 이제 우리의 Yesod 애플리케이션을 한 단계 더 발전시켜볼 거예요. 이번에는 RESTful API를 만들어볼 건데요, 어떠세요? 조금 설레지 않나요? Yesod를 사용하면 RESTful API도 정말 쉽게 만들 수 있답니다! 😎
RESTful API는 현대 웹 개발에서 정말 중요한 부분이에요. 프론트엔드와 백엔드를 분리하거나, 다른 서비스와 통신할 때 주로 사용하죠.
먼저, JSON을 다루기 위한 라이브러리를 추가해야 해요. package.yaml
파일의 dependencies 섹션에 다음 라인을 추가해주세요:
- aeson
그리고 Application.hs
파일을 다음과 같이 수정해볼게요:
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
import Yesod.Auth
import Yesod.Auth.Dummy
import Database.Persist.Sqlite
import Database.Persist.TH
import Data.Aeson hiding (json)
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Person
name Text
age Int
deriving Show
User
ident Text
password Text Maybe
UniqueUser ident
deriving Typeable
|]
instance ToJSON Person where
toJSON (Person name age) = object
[ "name" .= name
, "age" .= age
]
instance FromJSON Person where
parseJSON = withObject "Person" $ \v -> Person
<$> v .: "name"
<*> v .: "age"
data App = App ConnectionPool
mkYesod "App" [parseRoutes|
/ HomeR GET POST
/auth AuthR Auth getAuth
/api/persons ApiPersonsR GET POST
/api/person/#PersonId ApiPersonR GET PUT DELETE
|]
instance Yesod App where
authRoute _ = Just $ AuthR LoginR
isAuthorized (ApiPersonsR) _ = return Authorized
isAuthorized (ApiPersonR _) _ = return Authorized
isAuthorized HomeR _ = isAuthenticated
isAuthorized (AuthR _) _ = return Authorized
instance YesodPersist App where
type YesodPersistBackend App = SqlBackend
runDB action = do
App pool <- getYesod
runSqlPool action pool
instance YesodAuth App where
type AuthId App = UserId
authenticate creds = runDB $ do
x <- getBy $ UniqueUser $ credsIdent creds
case x of
Just (Entity uid _) -> return $ Authenticated uid
Nothing -> Authenticated <$> insert User
{ userIdent = credsIdent creds
, userPassword = Nothing
}
authPlugins _ = [authDummy]
instance RenderMessage App FormMessage where
renderMessage _ _ = defaultFormMessage
personForm :: Form Person
personForm = renderDivs $ Person
<$> areq textField "Name" Nothing
<*> areq intField "Age" Nothing
getHomeR :: Handler Html
getHomeR = do
(widget, enctype) <- generateFormPost personForm
persons <- runDB $ selectList [] [Desc PersonId]
defaultLayout $ do
setTitle "Welcome To Yesod!"
[whamlet|
<p>
<a href="@%7BAuthR" loginr>Login
$maybe _ <- maybeAuthId
</a><a href="@%7BAuthR" logoutr>Logout
<h1>Welcome to Yesod!
</h1><form method="post" action="@%7BHomeR%7D" enctype="#{enctype}">
^{widget}
<button>Submit
<h2>Registered Persons
<ul>
$forall Entity _ person <- persons
<li>#{personName person} (#{show $ personAge person} years old)
|]
postHomeR :: Handler Html
postHomeR = do
((result, _), _) <- runFormPost personForm
case result of
FormSuccess person -> do
runDB $ insert person
setMessage "Person added"
redirect HomeR
_ -> do
setMessage "Invalid input, please try again"
redirect HomeR
getApiPersonsR :: Handler Value
getApiPersonsR = do
persons <- runDB $ selectList [] [Desc PersonId]
return $ toJSON persons
postApiPersonsR :: Handler Value
postApiPersonsR = do
person <- requireJsonBody :: Handler Person
personId <- runDB $ insert person
return $ toJSON personId
getApiPersonR :: PersonId -> Handler Value
getApiPersonR personId = do
person <- runDB $ get404 personId
return $ toJSON person
putApiPersonR :: PersonId -> Handler Value
putApiPersonR personId = do
person <- requireJsonBody :: Handler Person
runDB $ replace personId person
return $ toJSON person
deleteApiPersonR :: PersonId -> Handler Value
deleteApiPersonR personId = do
runDB $ delete personId
return $ toJSON ("DELETED" :: Text)
main :: IO ()
main = runStderrLoggingT $ withSqlitePool "test.db" 10 $ \pool -> liftIO $ do
runResourceT $ flip runSqlPool pool $ do
runMigration migrateAll
warp 3000 $ App pool
</li></ul></h2></button></form></a></p>
우와, 코드가 더 길어졌죠? 하지만 걱정 마세요. 하나씩 설명해드릴게요! 😊
instance ToJSON Person
,instance FromJSON Person
: Person 타입을 JSON으로 변환하고, JSON에서 Person 타입으로 변환하는 방법을 정의했어요./api/persons ApiPersonsR GET POST
,/api/person/#PersonId ApiPersonR GET PUT DELETE
: API 엔드포인트를 추가했어요.getApiPersonsR
,postApiPersonsR
,getApiPersonR
,putApiPersonR
,deleteApiPersonR
: 각 API 엔드포인트에 대한 핸들러 함수를 구현했어요.
이제 이 코드를 저장하고 서버를 다시 실행해볼까요?
stack build && stack exec my-first-yesod-project
이제 우리의 RESTful API를 테스트해볼 수 있어요! curl 명령어를 사용해서 API를 호출해볼까요?
# 모든 Person 조회
curl http://localhost:3000/api/persons
# 새로운 Person 추가
curl -X POST -H "Content-Type: application/json" -d '{"name":"John Doe","age":30}' http://localhost:3000/api/persons
# 특정 Person 조회 (ID는 실제 존재하는 ID로 바꿔주세요)
curl http://localhost:3000/api/person/1
# Person 정보 수정 (ID는 실제 존재하는 ID로 바꿔주세요)
curl -X PUT -H "Content-Type: application/json" -d '{"name":"John Doe","age":31}' http://localhost:3000/api/person/1
# Person 삭제 (ID는 실제 존재하는 ID로 바꿔주세요)
curl -X DELETE http://localhost:3000/api/person/1
와우! 이제 우리의 Yesod 애플리케이션이 RESTful API를 제공하게 되었어요. 이 API를 사용하면 다른 애플리케이션에서도 우리의 Person 데이터를 쉽게 조회하고 관리할 수 있게 되었죠. 정말 멋지지 않나요? 🎉
이렇게 Yesod를 사용하면 RESTful API를 쉽고 안전하게 구현할 수 있어요. JSON 변환, 라우팅, 데이터베이스 작업 등이 모두 타입 안전하게 처리되니까 정말 편리하죠!
그런데 말이에요, 여러분! 혹시 이런 생각 들지 않나요? "와, 이제 진짜 백엔드 개발자가 된 것 같아!" 맞아요, 정확해요! 실제 웹 서비스들도 이런 방식으로 API를 제공하고 있어요. 재능넷 같은 플랫폼에서도 모바일 앱이나 프론트엔드 웹사이트와 통신하기 위해 이런 RESTful API를 사용하고 있겠죠? 여러분이 이 기술을 마스터하면, 정말 다재다능한 백엔드 개발자가 될 수 있을 거예요! 🚀💻
자, 이제 우리는 Yesod로 데이터베이스 연동, 폼 처리, 인증, 그리고 RESTful API까지 구현해봤어요. 어떠셨나요? 이제 정말 실제 웹 서비스를 만들 수 있는 수준이 되었죠? 다음 단계에서는 이 모든 것을 종합해서 실제 서비스를 구현해볼 거예요. 준비되셨나요? 그럼 고고! 🚀
8. 실전 프로젝트: 미니 블로그 만들기 📝
자, 이제 우리가 배운 모든 것을 종합해서 실제 서비스를 만들어볼 거예요. 어떤 서비스를 만들면 좋을까요? 음... 미니 블로그 어떠세요? 글을 쓰고, 읽고, 수정하고, 삭제할 수 있는 간단한 블로그 서비스를 만들어볼게요! 😃
이 미니 블로그 프로젝트를 통해 우리는 CRUD(Create, Read, Update, Delete) 작업, 인증, RESTful API 등 실제 웹 서비스에서 필요한 거의 모든 기능을 구현해볼 수 있어요.
먼저, 새로운 Yesod 프로젝트를 만들어볼까요?
stack new mini-blog yesod-sqlite
이제 mini-blog
디렉토리로 이동해서 src/Application.hs
파일을 열어주세요. 이 파일을 다음과 같이 수정해볼게요:
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
import Yesod.Auth
import Yesod.Auth.Dummy
import Database.Persist.Sqlite
import Database.Persist.TH
import Data.Time
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
User
ident Text
password Text Maybe
UniqueUser ident
deriving Typeable
BlogPost
title Text
content Text
author UserId
createdAt UTCTime default=CURRENT_TIME
deriving Show
|]
data App = App ConnectionPool
mkYesod "App" [parseRoutes|
/ HomeR GET
/blog BlogR GET POST
/blog/#BlogPostId BlogPostR GET PUT DELETE
/auth AuthR Auth getAuth
|]
instance Yesod App where
authRoute _ = Just $ AuthR LoginR
isAuthorized (BlogR) _ = isAuthenticated
isAuthorized (BlogPostR _) _ = isAuthenticated
isAuthorized _ _ = return Authorized
instance YesodPersist App where
type YesodPersistBackend App = SqlBackend
runDB action = do
App pool <- getYesod
runSqlPool action pool
instance YesodAuth App where
type AuthId App = UserId
authenticate creds = runDB $ do
x <- getBy $ UniqueUser $ credsIdent creds
case x of
Just (Entity uid _) -> return $ Authenticated uid
Nothing -> Authenticated <$> insert User
{ userIdent = credsIdent creds
, userPassword = Nothing
}
authPlugins _ = [authDummy]
instance RenderMessage App FormMessage where
renderMessage _ _ = defaultFormMessage
getHomeR :: Handler Html
getHomeR = defaultLayout [whamlet|
<h1>Welcome to Mini Blog!
</h1><p>
<a href="@%7BAuthR" loginr>Login
</a><a href="@%7BBlogR%7D">View all posts
|]
getBlogR :: Handler Html
getBlogR = do
posts <- runDB $ selectList [] [Desc BlogPostCreatedAt]
(widget, enctype) <- generateFormPost blogPostForm
defaultLayout $ do
[whamlet|
<h1>Blog Posts
<ul>
$forall Entity postId post <- posts
<li>
<a href="@%7BBlogPostR" postid>#{blogPostTitle post}
<h2>Create a new post
</h2><form method="post" action="@%7BBlogR%7D" enctype="#{enctype}">
^{widget}
<button>Submit
|]
postBlogR :: Handler Html
postBlogR = do
((result, _), _) <- runFormPost blogPostForm
case result of
FormSuccess blogPost -> do
userId <- requireAuthId
now <- liftIO getCurrentTime
_ <- runDB $ insert $ blogPost { blogPostAuthor = userId, blogPostCreatedAt = now }
setMessage "Blog post created"
redirect BlogR
_ -> do
setMessage "Invalid input, please try again"
redirect BlogR
getBlogPostR :: BlogPostId -> Handler Html
getBlogPostR postId = do
post <- runDB $ get404 postId
defaultLayout [whamlet|
<h1>#{blogPostTitle post}
</h1><p>#{blogPostContent post}
</p><p>Posted at: #{show $ blogPostCreatedAt post}
|]
putBlogPostR :: BlogPostId -> Handler Value
putBlogPostR postId = do
post <- requireJsonBody :: Handler BlogPost
runDB $ replace postId post
return $ toJSON post
deleteBlogPostR :: BlogPostId -> Handler Value
deleteBlogPostR postId = do
runDB $ delete postId
return $ toJSON ("DELETED" :: Text)
blogPostForm :: Form BlogPost
blogPostForm = renderDivs $ BlogPost
<$> areq textField "Title" Nothing
<*> areq textareaField "Content" Nothing
<*> pure undefined
<*> pure undefined
main :: IO ()
main = runStderrLoggingT $ withSqlitePool "blog.db" 10 $ \pool -> liftIO $ do
runResourceT $ flip runSqlPool pool $ do
runMigration migrateAll
warp 3000 $ App pool
</p></button></form></a></li></ul></h1></a></p>
우와, 코드가 정말 길어졌죠? 하지만 걱정 마세요. 이 코드는 우리가 지금까지 배운 모든 것을 종합한 거예요. 하나씩 설명해드릴게요! 😊
User
와BlogPost
모델: 사용자와 블로그 포스트 정보를 저장하기 위한 데이터 모델을 정의했어요.HomeR
,BlogR
,BlogPostR
: 각각 홈페이지, 블로그 목록 페이지, 개별 블로그 포스트 페이지를 위한 라우트예요.isAuthorized
: 블로그 관련 페이지는 로그인한 사용자만 접근할 수 있도록 설정했어요.getBlogR
,postBlogR
: 블로그 포스트 목록을 보여주고, 새 포스트를 작성하는 기능을 구현했어요.getBlogPostR
,putBlogPostR
,deleteBlogPostR
: 개별 블로그 포스트를 조회, 수정, 삭제하는 기능을 구현했어요.blogPostForm
: 블로그 포스트 작성을 위한 폼을 정의했어요.
이제 이 코드를 저장하고 서버를 실행해볼까요?
stack build && stack exec mini-blog
브라우저에서 http://localhost:3000
에 접속해보세요. 우리의 미니 블로그가 완성되었어요!
와우! 우리가 직접 만든 미니 블로그예요. 사용자는 로그인하고, 블로그 포스트를 작성하고, 읽고, 수정하고, 삭제할 수 있어요. 정말 멋지지 않나요? 🎉
이 미니 블로그 프로젝트를 통해 우리는 실제 웹 서비스에서 필요한 거의 모든 기능을 구현해봤어요. 데이터베이스 연동, 인증, 폼 처리, CRUD 작업, RESTful API 등 모든 것이 포함되어 있죠.
그런데 말이에요, 여러분! 혹시 이런 생각 들지 않나요? "와, 이제 정말 웹 개발자가 된 것 같아!" 맞아요, 정확해요! 여러분은 이제 실제 웹 서비스를 만들 수 있는 능력을 갖추게 되었어요. 재능넷 같은 플랫폼도 이런 기술들을 기반으로 만들어졌을 거예요. 여러분이 이 기술을 더 발전시키면, 언젠가는 여러분만의 멋진 웹 서비스를 만들 수 있을 거예요! 🚀💻
자, 이제 우리의 Yesod 여행이 거의 끝나가고 있어요. 어떠셨나요? 힘들기도 했지만, 정말 재미있고 보람찼죠? 마지막으로 우리가 배운 것들을 정리하고, 앞으로의 발전 방향에 대해 이야기해볼게요. 준비되셨나요? 그럼 마지막 여정을 떠나볼까요? 고고! 🚀
9. 마무리: 우리의 Yesod 여행을 돌아보며 🌟
와우! 정말 긴 여정이었죠? 우리는 Haskell과 Yesod를 사용해서 멋진 웹 애플리케이션을 만들어냈어요. 이제 우리의 여행을 돌아보고, 앞으로의 방향에 대해 이야기해볼게요. 😊
우리가 이 여행에서 배운 것들을 정리해볼까요?
- Haskell의 기초: 함수형 프로그래밍의 개념과 Haskell의 강력한 타입 시스템에 대해 배웠어요.
- Yesod 프레임워크 소개: Yesod의 특징과 장점에 대해 알아봤어요.
- "Hello, World!" 웹 서버 만들기: Yesod로 가장 기본적인 웹 서버를 구축해봤어요.
- 폼 처리하기: 사용자 입력을 안전하게 처리하는 방법을 배웠어요.
- 데이터베이스 연동: Persistent를 사용해 데이터를 저장하고 불러오는 방법을 익혔어요.
- 인증과 권한 관리: 사용자 로그인과 권한 관리 시스템을 구현해봤어요.
- RESTful API 만들기: JSON을 사용해 다른 서비스와 통신할 수 있는 API를 만들어봤어요.
- 실전 프로젝트: 미니 블로그: 배운 모든 것을 종합해 실제 서비스를 구현해봤어요.
정말 대단하지 않나요? 여러분은 이제 웹 개발의 거의 모든 핵심 개념을 이해하고 있어요! 🎉
하지만 이게 끝이 아니에요. 웹 개발의 세계는 정말 넓고 깊어요. 여러분의 Yesod 여행은 이제 막 시작된 거예요! 😄
앞으로 더 발전하고 싶다면, 다음과 같은 주제들을 더 공부해보는 것은 어떨까요?
- 고급 Haskell 기술: 모나드, 타입 클래스 등 Haskell의 더 깊은 개념들을 공부해보세요.
- 테스트 주도 개발(TDD): Yesod는 테스트를 쉽게 작성할 수 있도록 지원해요. TDD를 익혀보세요.
- 성능 최적화: Yesod는 이미 빠르지만, 더 최적화할 방법이 있어요.
- 배포와 운영: 실제 서비스를 배포하고 운영하는 방법을 배워보세요.
- 프론트엔드 개발: React나 Vue.js 같은 프론트엔드 프레임워크와 Yesod를 함께 사용해보세요.
그리고 가장 중요한 건, 계속해서 프로젝트를 만들어보는 거예요. 실제로 만들어보면서 배우는 것만큼 좋은 공부 방법은 없어요!
여러분, 정말 대단해요! 🌟 이렇게 어려운 여정을 끝까지 함께 해주셔서 감사합니다. 여러분은 이제 Haskell과 Yesod를 사용해 웹 개발을 할 수 있는 실력을 갖추게 되었어요. 이 기술들은 여러분의 개발자 경력에 큰 자산이 될 거예요.
혹시 이런 생각이 들지 않나요? "와, 이제 나도 재능넷 같은 플랫폼을 만들 수 있을 것 같아!" 맞아요, 여러분은 이제 그럴 능력이 충분해요! 물론 아직 배울 게 많지만, 여러분은 이미 웹 개발의 핵심을 이해하고 있어요. 앞으로 더 공부하고 경험을 쌓으면, 언젠가는 여러분만의 멋진 웹 서비스를 만들 수 있을 거예요. 🚀💻
마지막으로, 프로그래밍 세계에서 가장 중요한 것은 끊임없이 배우고 성장하는 자세예요. 기술은 계속 변화하고 발전하니까요. 하지만 걱정하지 마세요. 여러분은 이미 가장 어려운 첫 걸음을 뗐어요. 이제부터는 더 쉬워질 거예요!
자, 이제 정말 우리의 Yesod 여행이 끝났어요. 어떠셨나요? 힘들기도 했지만, 정말 재미있고 보람찼죠? 이 여행에서 배운 것들을 잘 활용해서 여러분만의 멋진 웹 세상을 만들어가세요. 항상 응원하고 있을게요! 화이팅! 👍😊