diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/event/Event.kt b/src/main/kotlin/kr/co/vividnext/sodalive/event/Event.kt
new file mode 100644
index 0000000..f529a46
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/event/Event.kt
@@ -0,0 +1,23 @@
+package kr.co.vividnext.sodalive.event
+
+import kr.co.vividnext.sodalive.common.BaseEntity
+import javax.persistence.Column
+import javax.persistence.Entity
+
+@Entity
+data class Event(
+    @Column(nullable = false)
+    var thumbnailImage: String,
+    @Column(nullable = true)
+    var detailImage: String?,
+    @Column(nullable = true)
+    var popupImage: String?,
+    @Column(nullable = true)
+    var link: String?,
+    @Column(nullable = true)
+    var title: String?,
+    @Column(nullable = false)
+    var isPopup: Boolean = false,
+    @Column(nullable = false)
+    var isActive: Boolean = true
+) : BaseEntity()
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/event/EventController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/event/EventController.kt
new file mode 100644
index 0000000..fdbe64b
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/event/EventController.kt
@@ -0,0 +1,13 @@
+package kr.co.vividnext.sodalive.event
+
+import kr.co.vividnext.sodalive.common.ApiResponse
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+@RequestMapping("/event")
+class EventController(private val service: EventService) {
+    @GetMapping
+    fun getEventList() = ApiResponse.ok(service.getEventList())
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/event/EventRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/event/EventRepository.kt
new file mode 100644
index 0000000..9b45e2f
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/event/EventRepository.kt
@@ -0,0 +1,35 @@
+package kr.co.vividnext.sodalive.event
+
+import com.querydsl.jpa.impl.JPAQueryFactory
+import kr.co.vividnext.sodalive.event.QEvent.event
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface EventRepository : JpaRepository<Event, Long>, EventQueryRepository
+
+interface EventQueryRepository {
+    fun getEventList(): List<EventItem>
+}
+
+@Repository
+class EventQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : EventQueryRepository {
+    override fun getEventList(): List<EventItem> {
+        return queryFactory
+            .select(
+                QEventItem(
+                    event.id,
+                    event.title,
+                    event.thumbnailImage,
+                    event.detailImage,
+                    event.popupImage,
+                    event.link,
+                    event.isPopup
+                )
+            )
+            .from(event)
+            .where(event.isActive.isTrue)
+            .orderBy(event.id.desc())
+            .fetch()
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/event/EventService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/event/EventService.kt
new file mode 100644
index 0000000..0469524
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/event/EventService.kt
@@ -0,0 +1,35 @@
+package kr.co.vividnext.sodalive.event
+
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.stereotype.Service
+
+@Service
+class EventService(
+    private val repository: EventRepository,
+
+    @Value("\${cloud.aws.cloud-front.host}")
+    private val cloudFrontHost: String
+) {
+    fun getEventList(): GetEventResponse {
+        val eventList = repository.getEventList()
+            .asSequence()
+            .map {
+                if (!it.thumbnailImageUrl.startsWith("https://")) {
+                    it.thumbnailImageUrl = "$cloudFrontHost/${it.thumbnailImageUrl}"
+                }
+
+                if (it.detailImageUrl != null && !it.detailImageUrl!!.startsWith("https://")) {
+                    it.detailImageUrl = "$cloudFrontHost/${it.detailImageUrl}"
+                }
+
+                if (it.popupImageUrl != null && !it.popupImageUrl!!.startsWith("https://")) {
+                    it.popupImageUrl = "$cloudFrontHost/${it.popupImageUrl}"
+                }
+
+                it
+            }
+            .toList()
+
+        return GetEventResponse(0, eventList)
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/event/GetEventResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/event/GetEventResponse.kt
new file mode 100644
index 0000000..5a0b6ee
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/event/GetEventResponse.kt
@@ -0,0 +1,18 @@
+package kr.co.vividnext.sodalive.event
+
+import com.querydsl.core.annotations.QueryProjection
+
+data class GetEventResponse(
+    val totalCount: Int,
+    val eventList: List<EventItem>
+)
+
+data class EventItem @QueryProjection constructor(
+    val id: Long,
+    val title: String? = null,
+    var thumbnailImageUrl: String,
+    var detailImageUrl: String? = null,
+    var popupImageUrl: String? = null,
+    val link: String? = null,
+    val isPopup: Boolean
+)
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/GetRecommendChannelResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/GetRecommendChannelResponse.kt
new file mode 100644
index 0000000..587673d
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/GetRecommendChannelResponse.kt
@@ -0,0 +1,8 @@
+package kr.co.vividnext.sodalive.live.recommend
+
+data class GetRecommendChannelResponse(
+    val creatorId: Long,
+    val nickname: String,
+    val profileImageUrl: String,
+    val isOnAir: Boolean
+)
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/GetRecommendLiveResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/GetRecommendLiveResponse.kt
new file mode 100644
index 0000000..97f21fe
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/GetRecommendLiveResponse.kt
@@ -0,0 +1,6 @@
+package kr.co.vividnext.sodalive.live.recommend
+
+data class GetRecommendLiveResponse(
+    val imageUrl: String,
+    val creatorId: Long
+)
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendController.kt
new file mode 100644
index 0000000..3c38eef
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendController.kt
@@ -0,0 +1,29 @@
+package kr.co.vividnext.sodalive.live.recommend
+
+import kr.co.vividnext.sodalive.common.ApiResponse
+import kr.co.vividnext.sodalive.common.SodaException
+import kr.co.vividnext.sodalive.member.Member
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+class LiveRecommendController(private val service: LiveRecommendService) {
+    @GetMapping("/live/recommend")
+    fun getRecommendLive(
+        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
+    ) = run {
+        if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
+
+        ApiResponse.ok(service.getRecommendLive(member))
+    }
+
+    @GetMapping("/live/recommend/channel")
+    fun getRecommendChannelList(
+        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
+    ) = run {
+        if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
+
+        ApiResponse.ok(service.getRecommendChannelList(member))
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendRepository.kt
new file mode 100644
index 0000000..7ebacf8
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendRepository.kt
@@ -0,0 +1,100 @@
+package kr.co.vividnext.sodalive.live.recommend
+
+import com.querydsl.core.types.Projections
+import com.querydsl.core.types.dsl.Expressions
+import com.querydsl.jpa.impl.JPAQueryFactory
+import kr.co.vividnext.sodalive.live.recommend.QRecommendLiveCreatorBanner.recommendLiveCreatorBanner
+import kr.co.vividnext.sodalive.live.room.LiveRoomType
+import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
+import kr.co.vividnext.sodalive.member.MemberRole
+import kr.co.vividnext.sodalive.member.QMember.member
+import org.springframework.stereotype.Repository
+import java.time.LocalDateTime
+
+@Repository
+class LiveRecommendRepository(private val queryFactory: JPAQueryFactory) {
+    fun getRecommendLive(memberId: Long, isAdult: Boolean): List<GetRecommendLiveResponse> {
+        val dateNow = LocalDateTime.now()
+
+        var where = recommendLiveCreatorBanner.startDate.loe(dateNow)
+            .and(recommendLiveCreatorBanner.endDate.goe(dateNow))
+
+        if (!isAdult) {
+            where = where.and(recommendLiveCreatorBanner.isAdult.isFalse)
+        }
+
+        return queryFactory
+            .select(
+                Projections.constructor(
+                    GetRecommendLiveResponse::class.java,
+                    recommendLiveCreatorBanner.image,
+                    recommendLiveCreatorBanner.creator.id
+                )
+            )
+            .from(recommendLiveCreatorBanner)
+            .where(where)
+            .orderBy(recommendLiveCreatorBanner.orders.asc())
+            .fetch()
+    }
+
+    fun getOnAirRecommendChannelList(
+        memberId: Long,
+        isAdult: Boolean
+    ): List<GetRecommendChannelResponse> {
+        var where = member.role.eq(MemberRole.CREATOR)
+            .and(member.isActive.isTrue)
+
+        if (!isAdult) {
+            where = where.and(liveRoom.isAdult.isFalse)
+        }
+
+        return queryFactory
+            .select(
+                Projections.constructor(
+                    GetRecommendChannelResponse::class.java,
+                    member.id,
+                    member.nickname,
+                    member.profileImage,
+                    Expressions.asBoolean(true)
+                )
+            )
+            .from(liveRoom)
+            .rightJoin(liveRoom.member, member)
+            .where(
+                where
+                    .and(liveRoom.isActive.isTrue)
+                    .and(liveRoom.type.ne(LiveRoomType.SECRET))
+                    .and(liveRoom.channelName.isNotNull)
+                    .and(liveRoom.channelName.isNotEmpty)
+            )
+            .groupBy(member.id)
+            .orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
+            .limit(20)
+            .fetch()
+    }
+
+    fun getRecommendChannelList(
+        memberId: Long,
+        withOutCreatorList: List<Long>,
+        limit: Long
+    ): List<GetRecommendChannelResponse> {
+        val where = member.role.eq(MemberRole.CREATOR)
+            .and(member.isActive.isTrue)
+
+        return queryFactory
+            .select(
+                Projections.constructor(
+                    GetRecommendChannelResponse::class.java,
+                    member.id,
+                    member.nickname,
+                    member.profileImage,
+                    Expressions.asBoolean(false)
+                )
+            )
+            .from(member)
+            .where(where.and(member.id.notIn(withOutCreatorList)))
+            .orderBy(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
+            .limit(limit)
+            .fetch()
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendService.kt
new file mode 100644
index 0000000..9b400ae
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendService.kt
@@ -0,0 +1,35 @@
+package kr.co.vividnext.sodalive.live.recommend
+
+import kr.co.vividnext.sodalive.member.Member
+import org.springframework.stereotype.Service
+
+@Service
+class LiveRecommendService(private val repository: LiveRecommendRepository) {
+
+    fun getRecommendLive(member: Member): List<GetRecommendLiveResponse> {
+        return repository.getRecommendLive(
+            memberId = member.id!!,
+            isAdult = member.auth != null
+        )
+    }
+
+    fun getRecommendChannelList(member: Member): List<GetRecommendChannelResponse> {
+        val onAirChannelList = repository.getOnAirRecommendChannelList(member.id!!, isAdult = member.auth != null)
+
+        if (onAirChannelList.size >= 20) {
+            return onAirChannelList
+        }
+
+        val onAirCreatorIdList = onAirChannelList.asSequence()
+            .map { it.creatorId }
+            .toList()
+
+        val notOnAirCreatorList = repository.getRecommendChannelList(
+            member.id!!,
+            withOutCreatorList = onAirCreatorIdList,
+            limit = (20 - onAirChannelList.size).toLong()
+        )
+
+        return onAirChannelList + notOnAirCreatorList
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/RecommendLiveCreatorBanner.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/RecommendLiveCreatorBanner.kt
new file mode 100644
index 0000000..78e823e
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/RecommendLiveCreatorBanner.kt
@@ -0,0 +1,28 @@
+package kr.co.vividnext.sodalive.live.recommend
+
+import kr.co.vividnext.sodalive.common.BaseEntity
+import kr.co.vividnext.sodalive.member.Member
+import java.time.LocalDateTime
+import javax.persistence.Column
+import javax.persistence.Entity
+import javax.persistence.FetchType
+import javax.persistence.JoinColumn
+import javax.persistence.ManyToOne
+
+@Entity
+data class RecommendLiveCreatorBanner(
+    @Column(nullable = false)
+    var image: String,
+    @Column(nullable = false)
+    var startDate: LocalDateTime,
+    @Column(nullable = false)
+    var endDate: LocalDateTime,
+    @Column(nullable = false)
+    var isAdult: Boolean = false,
+    @Column(nullable = false)
+    var orders: Int = 1
+) : BaseEntity() {
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "creator_id", nullable = false)
+    var creator: Member? = null
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/GetRoomListResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/GetRoomListResponse.kt
new file mode 100644
index 0000000..84f8796
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/GetRoomListResponse.kt
@@ -0,0 +1,19 @@
+package kr.co.vividnext.sodalive.live.room
+
+data class GetRoomListResponse(
+    val roomId: Long,
+    val title: String,
+    val content: String,
+    val beginDateTime: String,
+    val numberOfParticipate: Int,
+    val numberOfPeople: Int,
+    val coverImageUrl: String,
+    val isAdult: Boolean,
+    val price: Int,
+    val tags: List<String>,
+    val channelName: String?,
+    val managerNickname: String,
+    val managerId: Long,
+    val isReservation: Boolean,
+    val isPrivateRoom: Boolean
+)
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoom.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoom.kt
new file mode 100644
index 0000000..2578439
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoom.kt
@@ -0,0 +1,51 @@
+package kr.co.vividnext.sodalive.live.room
+
+import kr.co.vividnext.sodalive.common.BaseEntity
+import kr.co.vividnext.sodalive.member.Member
+import java.time.LocalDateTime
+import javax.persistence.Column
+import javax.persistence.Entity
+import javax.persistence.EnumType
+import javax.persistence.Enumerated
+import javax.persistence.FetchType
+import javax.persistence.JoinColumn
+import javax.persistence.OneToOne
+
+@Entity
+data class LiveRoom(
+    var title: String,
+    @Column(columnDefinition = "TEXT", nullable = false)
+    var notice: String,
+    var beginDateTime: LocalDateTime,
+    var numberOfPeople: Int,
+    var coverImage: String? = null,
+    var bgImage: String? = null,
+    var isAdult: Boolean,
+    val price: Int = 0,
+    @Enumerated(value = EnumType.STRING)
+    val type: LiveRoomType = LiveRoomType.OPEN,
+    @Column(nullable = true)
+    var password: String? = null
+) : BaseEntity() {
+    @OneToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "member_id", nullable = false)
+    var member: Member? = null
+
+    var channelName: String? = null
+    var isActive: Boolean = true
+}
+
+enum class LiveRoomType {
+    // 공개
+    OPEN,
+
+    // 비공개
+    PRIVATE,
+
+    // 비밀방
+    SECRET
+}
+
+enum class LiveRoomStatus {
+    NOW, RESERVATION
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomController.kt
new file mode 100644
index 0000000..ec927e4
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomController.kt
@@ -0,0 +1,29 @@
+package kr.co.vividnext.sodalive.live.room
+
+import kr.co.vividnext.sodalive.common.ApiResponse
+import kr.co.vividnext.sodalive.common.SodaException
+import kr.co.vividnext.sodalive.member.Member
+import org.springframework.data.domain.Pageable
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+@RequestMapping("/live/room")
+class LiveRoomController(private val service: LiveRoomService) {
+
+    @GetMapping
+    fun getRoomList(
+        @RequestParam timezone: String,
+        @RequestParam dateString: String? = null,
+        @RequestParam status: LiveRoomStatus,
+        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
+        pageable: Pageable
+    ) = run {
+        if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
+
+        ApiResponse.ok(service.getRoomList(dateString, status, pageable, member, timezone))
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt
new file mode 100644
index 0000000..d3ec2d2
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt
@@ -0,0 +1,119 @@
+package kr.co.vividnext.sodalive.live.room
+
+import com.querydsl.core.types.OrderSpecifier
+import com.querydsl.core.types.Predicate
+import com.querydsl.core.types.dsl.CaseBuilder
+import com.querydsl.core.types.dsl.Expressions
+import com.querydsl.jpa.impl.JPAQueryFactory
+import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
+import kr.co.vividnext.sodalive.member.Member
+import kr.co.vividnext.sodalive.member.QMember.member
+import org.springframework.data.domain.Pageable
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+import java.time.LocalDate
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+
+@Repository
+interface LiveRoomRepository : JpaRepository<LiveRoom, Long>, LiveRoomQueryRepository
+
+interface LiveRoomQueryRepository {
+    fun getLiveRoomList(
+        dateString: String?,
+        status: LiveRoomStatus,
+        pageable: Pageable,
+        member: Member,
+        timezone: String,
+        isAdult: Boolean
+    ): List<LiveRoom>
+}
+
+class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveRoomQueryRepository {
+    override fun getLiveRoomList(
+        dateString: String?,
+        status: LiveRoomStatus,
+        pageable: Pageable,
+        member: Member,
+        timezone: String,
+        isAdult: Boolean
+    ): List<LiveRoom> {
+        var where: Predicate
+
+        if (status == LiveRoomStatus.NOW) {
+            where = liveRoom.channelName.isNotNull
+                .and(liveRoom.channelName.isNotEmpty)
+        } else {
+            where = if (dateString != null) {
+                val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
+                val date = LocalDate.parse(dateString, dateTimeFormatter).atStartOfDay()
+                    .atZone(ZoneId.of(timezone))
+                    .withZoneSameInstant(ZoneId.of("UTC"))
+                    .toLocalDateTime()
+
+                liveRoom.beginDateTime.goe(date)
+                    .and(liveRoom.beginDateTime.lt(date.plusDays(1)))
+                    .and(
+                        liveRoom.channelName.isNull
+                            .or(liveRoom.channelName.isEmpty)
+                    )
+            } else {
+                liveRoom.beginDateTime.gt(
+                    LocalDateTime.now()
+                        .atZone(ZoneId.of(timezone))
+                        .withZoneSameInstant(ZoneId.of("UTC"))
+                        .toLocalDateTime()
+                )
+                    .and(
+                        liveRoom.channelName.isNull
+                            .or(liveRoom.channelName.isEmpty)
+                    )
+            }
+        }
+
+        if (!isAdult) {
+            where = where.and(liveRoom.isAdult.isFalse)
+        }
+
+        where = where.and(liveRoom.isActive.isTrue)
+            .and(liveRoom.member.isNotNull)
+            .and(liveRoom.type.ne(LiveRoomType.SECRET))
+
+        return queryFactory
+            .selectFrom(liveRoom)
+            .offset(pageable.offset)
+            .limit(pageable.pageSize.toLong())
+            .where(where)
+            .orderBy(
+                *orderByFieldAccountId(
+                    memberId = member.id!!,
+                    status = status,
+                    offset = pageable.offset,
+                    dateString = dateString
+                )
+            )
+            .fetch()
+    }
+
+    private fun orderByFieldAccountId(
+        memberId: Long,
+        status: LiveRoomStatus,
+        offset: Long,
+        dateString: String?
+    ): Array<out OrderSpecifier<*>> {
+        return if (status == LiveRoomStatus.NOW) {
+            arrayOf(Expressions.numberTemplate(Double::class.java, "function('rand')").asc())
+        } else if (status == LiveRoomStatus.RESERVATION && offset == 0L && dateString == null) {
+            arrayOf(
+                CaseBuilder()
+                    .`when`(member.id.eq(memberId)).then(1)
+                    .otherwise(2)
+                    .asc(),
+                liveRoom.beginDateTime.asc()
+            )
+        } else {
+            arrayOf(liveRoom.beginDateTime.asc())
+        }
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt
new file mode 100644
index 0000000..24a41b8
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt
@@ -0,0 +1,72 @@
+package kr.co.vividnext.sodalive.live.room
+
+import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfoRedisRepository
+import kr.co.vividnext.sodalive.member.Member
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.data.domain.Pageable
+import org.springframework.data.repository.findByIdOrNull
+import org.springframework.stereotype.Service
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+
+@Service
+class LiveRoomService(
+    private val repository: LiveRoomRepository,
+    private val roomInfoRepository: LiveRoomInfoRedisRepository,
+
+    @Value("\${cloud.aws.cloud-front.host}")
+    private val coverImageHost: String
+) {
+    fun getRoomList(
+        dateString: String?,
+        status: LiveRoomStatus,
+        pageable: Pageable,
+        member: Member,
+        timezone: String
+    ): List<GetRoomListResponse> {
+        return repository
+            .getLiveRoomList(
+                dateString = dateString,
+                status = status,
+                pageable = pageable,
+                member = member,
+                timezone = timezone,
+                isAdult = member.auth != null
+            )
+            .asSequence()
+            .map {
+                val roomInfo = roomInfoRepository.findByIdOrNull(it.id!!)
+
+                val beginDateTime = it.beginDateTime
+                    .atZone(ZoneId.of("UTC"))
+                    .withZoneSameInstant(ZoneId.of(timezone))
+
+                GetRoomListResponse(
+                    roomId = it.id!!,
+                    title = it.title,
+                    content = it.notice,
+                    beginDateTime = beginDateTime.format(
+                        DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")
+                    ),
+                    numberOfParticipate = (roomInfo?.listenerCount ?: 0) +
+                        (roomInfo?.speakerCount ?: 0) +
+                        (roomInfo?.managerCount ?: 0),
+                    numberOfPeople = it.numberOfPeople,
+                    isAdult = it.isAdult,
+                    price = it.price,
+                    channelName = it.channelName,
+                    managerNickname = it.member!!.nickname,
+                    managerId = it.member!!.id!!,
+                    tags = listOf(),
+                    coverImageUrl = if (it.coverImage!!.startsWith("https://")) {
+                        it.coverImage!!
+                    } else {
+                        "$coverImageHost/${it.coverImage!!}"
+                    },
+                    isReservation = false,
+                    isPrivateRoom = it.type == LiveRoomType.PRIVATE
+                )
+            }
+            .toList()
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/donation/LiveRoomDonationMessage.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/donation/LiveRoomDonationMessage.kt
new file mode 100644
index 0000000..4655c93
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/donation/LiveRoomDonationMessage.kt
@@ -0,0 +1,10 @@
+package kr.co.vividnext.sodalive.live.room.donation
+
+import java.util.UUID
+
+data class LiveRoomDonationMessage(
+    val uuid: String = UUID.randomUUID().toString(),
+    val nickname: String,
+    val coinMessage: String,
+    val donationMessage: String
+)
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/info/LiveRoomInfo.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/info/LiveRoomInfo.kt
new file mode 100644
index 0000000..d666ae2
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/info/LiveRoomInfo.kt
@@ -0,0 +1,101 @@
+package kr.co.vividnext.sodalive.live.room.info
+
+import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationMessage
+import kr.co.vividnext.sodalive.member.Member
+import org.springframework.data.annotation.Id
+import org.springframework.data.redis.core.RedisHash
+
+@RedisHash("live_room_info")
+data class LiveRoomInfo(
+    @Id
+    val roomId: Long,
+    var speakerList: List<LiveRoomMember> = mutableListOf(),
+    var listenerList: List<LiveRoomMember> = mutableListOf(),
+    var managerList: List<LiveRoomMember> = mutableListOf(),
+    var donationMessageList: List<LiveRoomDonationMessage> = mutableListOf()
+) {
+    var speakerCount = 0
+        private set
+
+    var listenerCount = 0
+        private set
+
+    var managerCount = 0
+        private set
+
+    fun addSpeaker(member: Member) {
+        val liveRoomMember = LiveRoomMember(member)
+        liveRoomMember.role = LiveRoomMemberRole.SPEAKER
+
+        val speakerSet = speakerList.toMutableSet()
+        speakerSet.add(liveRoomMember)
+        speakerList = speakerSet.toList()
+
+        setSpeakerCount()
+    }
+
+    fun removeSpeaker(member: Member) {
+        (speakerList as MutableList).removeIf { it.id == member.id!! }
+        setSpeakerCount()
+    }
+
+    private fun setSpeakerCount() {
+        speakerCount = speakerList.size
+    }
+
+    fun addListener(member: Member) {
+        val liveRoomMember = LiveRoomMember(member)
+        liveRoomMember.role = LiveRoomMemberRole.LISTENER
+
+        val listenerSet = listenerList.toMutableSet()
+        listenerSet.add(liveRoomMember)
+        listenerList = listenerSet.toList()
+
+        setListenerCount()
+    }
+
+    fun removeListener(member: Member) {
+        (listenerList as MutableList).removeIf { it.id == member.id!! }
+        setListenerCount()
+    }
+
+    private fun setListenerCount() {
+        listenerCount = listenerList.size
+    }
+
+    fun addManager(member: Member) {
+        val liveRoomMember = LiveRoomMember(member)
+        liveRoomMember.role = LiveRoomMemberRole.MANAGER
+
+        val managerSet = managerList.toMutableSet()
+        managerSet.add(liveRoomMember)
+        managerList = managerSet.toList()
+
+        setManagerCount()
+    }
+
+    fun removeManager(member: Member) {
+        (managerList as MutableList).removeIf { it.id == member.id!! }
+        setManagerCount()
+    }
+
+    private fun setManagerCount() {
+        managerCount = managerList.size
+    }
+
+    fun addDonationMessage(nickname: String, coin: Int, donationMessage: String) {
+        val donationMessageSet = donationMessageList.toMutableSet()
+        donationMessageSet.add(
+            LiveRoomDonationMessage(
+                nickname = nickname,
+                coinMessage = "${coin}코인을 후원하셨습니다.",
+                donationMessage = donationMessage
+            )
+        )
+        donationMessageList = donationMessageSet.toList()
+    }
+
+    fun removeDonationMessage(uuid: String) {
+        (donationMessageList as MutableList).removeIf { it.uuid == uuid }
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/info/LiveRoomInfoRedisRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/info/LiveRoomInfoRedisRepository.kt
new file mode 100644
index 0000000..a39c911
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/info/LiveRoomInfoRedisRepository.kt
@@ -0,0 +1,5 @@
+package kr.co.vividnext.sodalive.live.room.info
+
+import org.springframework.data.repository.CrudRepository
+
+interface LiveRoomInfoRedisRepository : CrudRepository<LiveRoomInfo, Long>
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/info/LiveRoomMember.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/info/LiveRoomMember.kt
new file mode 100644
index 0000000..cdca980
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/info/LiveRoomMember.kt
@@ -0,0 +1,21 @@
+package kr.co.vividnext.sodalive.live.room.info
+
+import kr.co.vividnext.sodalive.member.Member
+
+data class LiveRoomMember(
+    val id: Long,
+    val nickname: String,
+    val profileImage: String
+) {
+    var role = LiveRoomMemberRole.LISTENER
+
+    constructor(member: Member) : this(
+        id = member.id!!,
+        nickname = member.nickname,
+        profileImage = member.profileImage ?: "profile/default-profile.png"
+    )
+}
+
+enum class LiveRoomMemberRole {
+    LISTENER, SPEAKER, MANAGER
+}