构建 DevShow —— 一个社区展示网站

在本教程中,你将构建 DevShow。DevShow 是一个小型的社区展示网站,用户可以在这里分享自己构建的作品。 每个用户都能创建账户、发布一条"展示条目"(一个项目、工具、实验,或任何他们引以为傲的东西),并浏览他人创建的条目。

概述

本教程采用动手实践的方式,从零到一构建一个真实的应用。你不会孤立地学习各个功能,而是能看到 AdonisJS 中的一切是如何协同工作的:路由、控制器、模型、验证、身份验证与模板引擎汇聚在一起,构成一个可运行的 Web 应用

到本教程结束时,你将构建出:

  • 帖子列表与详情页 - 展示所有帖子以及带评论的单篇帖子详情
  • 帖子的创建与编辑 - 通过表单创建和更新帖子,并带验证
  • 评论系统 - 允许用户对帖子发表评论
  • 授权 - 确保用户只能编辑/删除自己的帖子与评论
  • 导航与样式 - 带有页面间合理导航的精致 UI

身份验证系统(注册、登录、登出)已包含在入门套件中,且功能完整。

理解入门套件

我们以 AdonisJS Hypermedia 入门套件为起点,它已经内置了身份验证。让我们通过打开路由文件,看看手头有哪些可用资源。

start/routes.ts
import { middleware } from '#start/kernel'
import { controllers } from '#generated/controllers'
import router from '@adonisjs/core/services/router'

router.on('/').render('pages/home').as('home')

/**
 * 注册和登录路由 - 仅对访客开放
 */
router
  .group(() => {
    router.get('signup', [controllers.NewAccount, 'create'])
    router.post('signup', [controllers.NewAccount, 'store'])

    router.get('login', [controllers.Session, 'create'])
    router.post('login', [controllers.Session, 'store'])
  })
  .use(middleware.guest())

/**
 * 登出路由 - 仅对已登录用户开放
 */
router
  .group(() => {
    router.post('logout', [controllers.Session, 'destroy'])
  })
  .use(middleware.auth())

入门套件为我们提供了用户注册、登录与登出的路由。注意 middleware.guest() 如何确保只有未登录用户才能访问注册/登录页,而 middleware.auth() 则保护登出路由。

Note

在整个教程中,我们都会使用 auth 中间件来保护需要身份验证的路由。

控制器如何工作

让我们看看注册控制器,了解请求是如何在应用中流转的。

app/controllers/new_account_controller.ts
import User from '#models/user'
import { signupValidator } from '#validators/user'
import type { HttpContext } from '@adonisjs/core/http'

export default class NewAccountController {
  async create({ view }: HttpContext) {
    return view.render('pages/auth/signup')
  }

  async store({ request, response, auth }: HttpContext) {
    /**
     * 验证提交的数据
     */
    const payload = await request.validateUsing(signupValidator)
    
    /**
     * 在数据库中创建新用户
     */
    const user = await User.create(payload)

    /**
     * 自动登录
     */
    await auth.use('web').login(user)
    
    /**
     * 重定向到首页
     */
    response.redirect().toRoute('home')
  }
}

每个控制器方法都以 HTTP 上下文对象作为第一个参数。该上下文包含了当前请求的一切信息:请求数据、响应对象、认证状态、视图渲染器等。我们只需解构出需要的属性(view 用于渲染模板,request 用于表单数据,response 用于重定向,auth 用于身份验证)。

create 方法只是展示注册表单。store 方法则承担了主要工作:它验证数据、创建用户、登录并重定向到首页。这种将验证器、模型与认证汇集在一起的模式,将会贯穿整个教程

你可能会注意到控制器引用了 User 模型和一个 signupValidator。入门套件已经包含了它们。我们将在 数据库与模型 章节了解模型如何工作,在 表单与验证 章节了解验证器。

视图如何工作

当控制器调用 view.render('pages/auth/signup') 时,AdonisJS 会查找一个模板文件并将其渲染为 HTML。让我们看看这个注册视图长什么样。

resources/views/pages/auth/signup.edge
@layout()
  <div class="form-container">
    <div>
      <h1> Signup </h1>
      <p>
        Enter your details below to create your account
      </p>
    </div>

    <div>
      @form({ route: 'new_account.store', method: 'POST' })
        <div>
          @field.root({ name: 'fullName' })
            @!field.label({ text: 'Full name' })
            @!input.control()
            @!field.error()
          @end
        </div>

        <div>
          @field.root({ name: 'email' })
            @!field.label({ text: 'Email' })
            @!input.control({ type: 'email', autocomplete: 'email' })
            @!field.error()
          @end
        </div>

        <div>
          @field.root({ name: 'password' })
            @!field.label({ text: 'Password' })
            @!input.control({ type: 'password', autocomplete: 'new-password' })
            @!field.error()
          @end
        </div>

        <div>
          @field.root({ name: 'passwordConfirmation' })
            @!field.label({ text: 'Confirm password' })
            @!input.control({ type: 'password', autocomplete: 'new-password' })
            @!field.error()
          @end
        </div>

        <div>
          @!button({ text: 'Sign up', type: 'submit' })
        </div>
      @end
    </div>
  </div>
@end

视图位于 resources/views 目录中。AdonisJS 使用 Edge 作为模板引擎。Edge 模板看起来与 HTML 相似,但带有以 @ 开头的特殊标签。

@layout() 标签用通用布局(页头、页脚、CSS)包裹页面内容。@form()@field.root() 标签是入门套件自带的组件。它们会渲染标准的 HTML 表单元素,并内置 CSRF 防护与验证错误展示等功能。

当你访问 /signup 时,路由调用控制器的 create 方法,该方法渲染此视图,Edge 将其转换为浏览器显示的 HTML。

试着创建一个账户

在继续之前,先用 node ace serve --hmr 启动你的开发服务器,并尝试创建一个账户。先熟悉一下入门套件的工作方式,我们将在此基础上继续构建。