diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/dev/ptnr/frontalobebackend/controller/BlogApiController.java b/src/main/java/dev/ptnr/frontalobebackend/controller/BlogApiController.java
new file mode 100644
index 0000000..c14502f
--- /dev/null
+++ b/src/main/java/dev/ptnr/frontalobebackend/controller/BlogApiController.java
@@ -0,0 +1,23 @@
+package dev.ptnr.frontalobebackend.controller;
+
+import dev.ptnr.frontalobebackend.domain.Article;
+import dev.ptnr.frontalobebackend.dto.AddArticleRequest;
+import dev.ptnr.frontalobebackend.service.BlogService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RequiredArgsConstructor
+@RestController // HTTP Response Body의 객체 데이터를 JSON 형식으로 반환하는 컨트롤러
+public class BlogApiController {
+ private final BlogService blogService;
+
+ @PostMapping("/api/articles")
+ public ResponseEntity addArticle(@RequestBody AddArticleRequest request) {
+ Article savedArticle = blogService.save(request);
+ return ResponseEntity.status(HttpStatus.CREATED).body(savedArticle);
+ }
+}
diff --git a/src/main/java/dev/ptnr/frontalobebackend/domain/Article.java b/src/main/java/dev/ptnr/frontalobebackend/domain/Article.java
new file mode 100644
index 0000000..52be7fd
--- /dev/null
+++ b/src/main/java/dev/ptnr/frontalobebackend/domain/Article.java
@@ -0,0 +1,29 @@
+package dev.ptnr.frontalobebackend.domain;
+
+import jakarta.persistence.*;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Entity
+@Getter
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class Article {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "id", updatable = false)
+ private Long id;
+
+ @Column(name = "title", nullable = false)
+ private String title;
+
+ @Column(name = "content", nullable = false)
+ private String content;
+
+ @Builder
+ public Article(String title, String content) {
+ this.title = title;
+ this.content = content;
+ }
+}
diff --git a/src/main/java/dev/ptnr/frontalobebackend/dto/AddArticleRequest.java b/src/main/java/dev/ptnr/frontalobebackend/dto/AddArticleRequest.java
new file mode 100644
index 0000000..d762d02
--- /dev/null
+++ b/src/main/java/dev/ptnr/frontalobebackend/dto/AddArticleRequest.java
@@ -0,0 +1,18 @@
+package dev.ptnr.frontalobebackend.dto;
+
+import dev.ptnr.frontalobebackend.domain.Article;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+public class AddArticleRequest {
+ private String title;
+ private String content;
+
+ public Article toEntity() {
+ return Article.builder().title(title).content(content).build();
+ }
+}
diff --git a/src/main/java/dev/ptnr/frontalobebackend/dto/ArticleResponse.java b/src/main/java/dev/ptnr/frontalobebackend/dto/ArticleResponse.java
new file mode 100644
index 0000000..f70b104
--- /dev/null
+++ b/src/main/java/dev/ptnr/frontalobebackend/dto/ArticleResponse.java
@@ -0,0 +1,15 @@
+package dev.ptnr.frontalobebackend.dto;
+
+import dev.ptnr.frontalobebackend.domain.Article;
+import lombok.Getter;
+
+@Getter
+public class ArticleResponse {
+ private final String title;
+ private final String content;
+
+ public ArticleResponse(Article article) {
+ this.title = article.getTitle();
+ this.content = article.getContent();
+ }
+}
diff --git a/src/main/java/dev/ptnr/frontalobebackend/repository/BlogRepository.java b/src/main/java/dev/ptnr/frontalobebackend/repository/BlogRepository.java
new file mode 100644
index 0000000..74fbe9b
--- /dev/null
+++ b/src/main/java/dev/ptnr/frontalobebackend/repository/BlogRepository.java
@@ -0,0 +1,8 @@
+package dev.ptnr.frontalobebackend.repository;
+
+import dev.ptnr.frontalobebackend.domain.Article;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface BlogRepository extends JpaRepository {
+
+}
diff --git a/src/main/java/dev/ptnr/frontalobebackend/service/BlogService.java b/src/main/java/dev/ptnr/frontalobebackend/service/BlogService.java
new file mode 100644
index 0000000..f5b032f
--- /dev/null
+++ b/src/main/java/dev/ptnr/frontalobebackend/service/BlogService.java
@@ -0,0 +1,24 @@
+package dev.ptnr.frontalobebackend.service;
+
+import dev.ptnr.frontalobebackend.domain.Article;
+import dev.ptnr.frontalobebackend.dto.AddArticleRequest;
+import dev.ptnr.frontalobebackend.repository.BlogRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@RequiredArgsConstructor // final or @NotNull이 붙은 생성자를 만들어 줌
+@Service // BlogService as Spring Bean
+public class BlogService {
+ private final BlogRepository blogRepository;
+
+ // Add Blog Article
+ public Article save(AddArticleRequest request) {
+ return blogRepository.save(request.toEntity());
+ }
+
+ public List findAll() {
+ return blogRepository.findAll();
+ }
+}
diff --git a/src/test/java/dev/ptnr/frontalobebackend/controller/BlogApiControllerTest.java b/src/test/java/dev/ptnr/frontalobebackend/controller/BlogApiControllerTest.java
new file mode 100644
index 0000000..380fe36
--- /dev/null
+++ b/src/test/java/dev/ptnr/frontalobebackend/controller/BlogApiControllerTest.java
@@ -0,0 +1,72 @@
+package dev.ptnr.frontalobebackend.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import dev.ptnr.frontalobebackend.domain.Article;
+import dev.ptnr.frontalobebackend.dto.AddArticleRequest;
+import dev.ptnr.frontalobebackend.repository.BlogRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import java.util.List;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@SpringBootTest
+@AutoConfigureMockMvc
+class BlogApiControllerTest {
+ @Autowired
+ protected MockMvc mockMvc;
+
+ @Autowired
+ protected ObjectMapper objectMapper; // Marshal, Unmarshal을 위한 클래스
+
+ @Autowired
+ private WebApplicationContext context;
+
+ @Autowired
+ BlogRepository blogRepository;
+
+ @BeforeEach
+ public void mockMvcSetup() {
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
+ blogRepository.deleteAll();
+ }
+
+ @DisplayName("addArticle: 블로그 글 추가 성공 확인")
+ @Test
+ public void addArticle() throws Exception {
+ // given
+ final String url = "/api/articles";
+ final String title = "title";
+ final String content = "content";
+ final AddArticleRequest userRequest = new AddArticleRequest(title, content);
+
+ // Marshal JSON
+ final String requestBody = objectMapper.writeValueAsString(userRequest);
+
+ // when
+ // given에서 설정한 내용을 바탕으로 요청 전송
+ ResultActions result = mockMvc.perform(post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody));
+
+ // then
+ result.andExpect(status().isCreated());
+
+ List articles = blogRepository.findAll();
+
+ assertThat(articles.size()).isEqualTo(1);
+ assertThat(articles.get(0).getTitle()).isEqualTo(title);
+ assertThat(articles.get(0).getContent()).isEqualTo(content);
+ }
+}
\ No newline at end of file