如何用Adonis.js构建一个RESTful API

417 阅读8分钟

用Adonis.js构建RESTful API

在不断变化的前端和后端框架的世界中,学习和构建RESTful API对任何软件工程师来说都是一项高要求的技能。Adonis.js可以让你掌握技能和知识,成为一个全栈开发者。

简介

"RESTful API使用HTTP请求在使用端点(路由)的各方之间交换数据。这些[HTTP请求]包括GET、PUT、POST和DELETE"。

我们将向你展示如何使用Adonis.js 5 创建一个RESTful API。我们将为一个论坛建立一个API。我们将涵盖认证和授权等方面。该教程还涵盖了结构化和构建HTTPS端点的最佳实践以及行业标准的响应。

Adonis.js是JavaScript中的[Laravel],对它的良好掌握是一种高需求的技能。因此,学会用Adonis.js构建你的第一个REST API是一个游戏规则的改变。

概要

  • 简介
  • 设置Adonis.js
  • 创建数据库
  • 设置授权和认证
  • 创建模型
  • 创建控制器
  • 创建端点路由
  • 测试论坛的API
  • 总结

设置Adonis.js 5

如果你以前一直在使用JavaScript框架,你应该已经在你的电脑上安装了Node.js

Adonis.js需要Node.js >=12.x.x和NPM >=6.x.x,通过检查电脑上安装的版本,确保你有所需的Node.js。

我们将假设你有这些要求,所以你可以继续通过运行这个命令创建一个新的Adonis.js 5项目。

    npm init adonis-ts-app adonisjs-forum-api

当你运行该命令时,如果要求你选择项目结构,请选择API Server ,然后继续,其他选项保持默认。

安装成功后,用你喜欢的文本编辑器打开文件夹,在终端运行以下命令。

    cd <PROJECT_NAME>
    node ace serve --watch

打开你的浏览器,访问呈现的URL,如果你看到hello world

恭喜你。

创建数据库

现在我们已经创建了我们的第一个Hello World API,如果没有一个用于存储、读取和更新数据的数据库,我们的论坛API是不完整的。

让我们马上建立我们的数据库。

你需要使用你选择的任何数据库客户端,用MySQL创建一个数据库。

为了无缝地访问和操作数据库,让我们用这个命令安装Adonis.js使用的LUCID ORM。

    npm i @adonisjs/lucid@alpha

然后,你需要通过运行invoke 命令并按照说明,将其与你刚刚创建的数据库进行配置。

    node ace invoke @adonisjs/lucid

当你运行上述命令时,你会看到不同的数据库选项。在本教程中,我们将使用MySQL/MariaDB,然后选择In the Terminal ,进行说明。

仔细阅读说明,用你的数据库凭证更新你的.env 文件,如下所示。

    DB_CONNECTION=mysql
    MYSQL_USER= //DB_USER
    MYSQL_HOST=localhost
    MYSQL_DB_NAME= //DB_NAME
    MYSQL_PORT=3306
    MYSQL_PASSWORD= //DB_PASSWORD

如果你想改变数据库的默认配置,你可以随时到config/database.ts ,配置一些凭证。

如果你在测试你的论坛API时遇到这个错误Client does not support authentication protocol requested by server; ,请按照以下步骤来解决。

    npm install mysql2

然后打开config/database.ts ,将client 字段更新为mysql2

这就是全部。

设置授权和认证

在Adonis.js 5中,认证和授权的设置非常简单。你所需要做的就是安装Auth包,其他所有复杂的认证逻辑都已经为你内置。

让我们开始吧。

用这个命令安装Auth包。

    npm i @adonisjs/auth@alpha

像往常一样,用invoke 命令调用Auth包来配置它。

    node ace invoke @adonisjs/auth

它将要求你选择提供者,在这种情况下,我选择了Lucid ,接下来是API Token ,因为我们正在建立一个API。

  1. 为你的认证输入User Model。
  2. 然后按Y 键,为它创建一个迁移。
  3. 接下来,选择Database 作为你的提供者。
  4. 现在再按Y ,为api_tokens 创建一个迁移。

现在,你的database/migrations 文件夹中应该有两个迁移文件。更新xxxxx_users.ts 文件,包括一个name 和你选择的任何其他列。

现在,将auth 中间件添加到start/kernel.ts 中的kernel.ts 文件。

Server.middleware.registerNamed({
  auth: "App/Middleware/Auth",
});

创建迁移程序

我们应该为后面要使用的Post和Forum模型创建剩余的迁移。

让我们开始吧。

使用这个命令创建一个新的迁移。

    node ace make:migration posts

运行该命令后,在database/migrations/xxxx_posts.ts 中打开新文件,然后粘贴下面的代码。

import BaseSchema from "@ioc:Adonis/Lucid/Schema";
export default class Posts extends BaseSchema {
  protected tableName = "posts";
  public async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments("id");
      table.string("title", 255).notNullable();
      table.string("content", 255).notNullable();
      table.integer("user_id", 180).notNullable();
      table.integer("forum_id").nullable();
      table.timestamps(true);
    });
  }
  public async down() {
    this.schema.dropTable(this.tableName);
  }
}

现在,我们要保持简单,为posts 创建我们的数据库模式,该模式将包含代码中列出的下列列,而不需要定义任何数据库约束。

接下来,我们将创建Forum 的模式,并将以下代码也粘贴进去。

`node ace make:migration forums`

还有下面的代码。

import BaseSchema from "@ioc:Adonis/Lucid/Schema";
export default class Forums extends BaseSchema {
  protected tableName = "forums";
  public async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments("id");
      table.string("title", 255).notNullable();
      table.string("description", 255).notNullable();
      table.integer("user_id", 180).notNullable();
      table.timestamps(true);
    });
  }
  public async down() {
    this.schema.dropTable(this.tableName);
  }
}

我们也将保持这个简单,为posts 创建我们的数据库模式,包含代码中列出的以下列,不定义任何数据库约束。

public async uppublic async down 也是BaseSchema 对象的两个重要方法。up 方法负责运行迁移和创建数据库模式,而down 方法也用于放弃创建的模式/表。

接下来,我们将运行迁移,按照迁移中的规定生成和创建数据库表。

为了运行我们的迁移,我们需要停止服务器并再次启动它。

    node ace serve --watch
    // Then
    node ace migration:run

创建模型

现在,我们将创建这个API需要的所有模型,并正确配置它们与我们的数据库进行交互。

    node ace make:model Forum
    node ace make:model Post

你可以克隆我的资源库,看看我们如何映射列和配置关系。

这是一个User 模型的预览,模型的样子。

import { DateTime } from "luxon";
import Post from "App/Models/Post";
import Forum from "App/Models/Forum";
import Hash from "@ioc:Adonis/Core/Hash";
import {
  column,
  beforeSave,
  BaseModel,
  hasMany,
  HasMany,
} from "@ioc:Adonis/Lucid/Orm";
export default class User extends BaseModel {
  @column({ isPrimary: true })
  public id: number;
  @column()
  public email: string;
  @column()
  public name: string;
  @column({ serializeAs: null })
  public password: string;
  @column()
  public rememberMeToken?: string;
  @column.dateTime({ autoCreate: true })
  public createdAt: DateTime;
  @column.dateTime({ autoCreate: true, autoUpdate: true })
  public updatedAt: DateTime;
  @hasMany(() => Post)
  public posts: HasMany<typeof Post>;
  @hasMany(() => Forum)
  public forums: HasMany<typeof Forum>;
  @beforeSave()
  public static async hashPassword(user: User) {
    if (user.$dirty.password) {
      user.password = await Hash.make(user.password);
    }
  }
}

上面的代码分别创建了PostForum 模型,并使用columns 装饰器映射了不同的列。它还指定了每一列的数据类型。

许多其他的装饰器,如hasMany ,为one-to-many database relationship ,在模型上定义。

创建控制器

Models在这一步,我们将为上述controllers 。在此之前,让我们为认证创建一个AuthController。

    node ace make:controller Auth

打开app/Controllers/Http/AuthController.ts 中的文件,粘贴下面的代码。

import { HttpContextContract } from "@ioc:Adonis/Core/HttpContext";
import User from "App/Models/User";

export default class AuthController {
  public async login({ request, auth }: HttpContextContract) {
    const email = request.input("email");
    const password = request.input("password");
    const token = await auth.use("api").attempt(email, password, {
      expiresIn: "10 days",
    });
    return token.toJSON();
  }
  public async register({ request, auth }: HttpContextContract) {
    const email = request.input("email");
    const password = request.input("password");
    const name = request.input("name");
    const newUser = new User();
    newUser.email = email;
    newUser.password = password;
    newUser.name = name;
    await newUser.save();
    const token = await auth.use("api").login(newUser, {
      expiresIn: "10 days",
    });
    return token.toJSON();
  }
}

上面的代码只是注册和登录一个用户,没有任何复杂的验证和错误处理。所以,它很容易理解。

接下来,让我们为我们的API ,一次性创建所有的controllers

    node ace make:controller Post
    node ace make:controller Forum

打开PostsController.ts 文件,该文件位于app/Controllers/Http 文件夹内,并添加以下代码。

    import { HttpContextContract } from "@ioc:Adonis/Core/HttpContext";
    import Post from "App/Models/Post";
    export default class PostsController {
         public async index({ request}: HttpContextContract)
        {
            const posts = await Post.query().preload('user').preload('forum');
            return posts
        }
        public async show({ request, params}: HttpContextContract)
        {
            try {
                const post = await Post.find(params.id);
                if(post){
                    await post.preload('user')
                    await post.preload('forum');
                    return post
                }
            } catch (error) {
                console.log(error)
            }

        }

        public async update({ auth, request, params}: HttpContextContract)
        {
            const post = await Post.find(params.id);
            if (post) {
                post.title = request.input('title');
                post.content = request.input('content');
                if (await post.save()) {
                    await post.preload('user')
                    await post.preload('forum')
                    return post
                }
                return; // 422
            }
            return; // 401
        }

        public async store({ auth request, response}: HttpContextContract)
        {
            const user = await auth.authenticate();
            const post = new Post();
            post.title = request.input('title');
            post.content = request.input('content');
            post.forumId = request.input('forum');
            await user.related('posts').save(post)
            return post
        }
        public async destroy({response, auth, request, params}: HttpContextContract)
        {
           const user = await auth.authenticate();
           const post = await Post.query().where('user_id', user.id).where('id', params.id).delete();
           return response.redirect('/dashboard');
        }
    }

上面的代码代表了我们Post 模型的API的CRUD功能。它包含了我们应用程序的业务逻辑的不同方法,如删除(destroy) ,更新(update) ,存储(store) ,以及在数据库中检索(index) 帖子。

创建端点路由

下一步是为我们的前端或移动应用程序创建端点。

要做到这一点,打开start 文件夹中的route.ts 文件,添加以下代码。

//......
Route.group(() => {
  Route.post("register", "AuthController.register");
  Route.post("login", "AuthController.login");
  Route.group(() => {
    Route.resource("posts", "PostsController").apiOnly();
    Route.resource("forums", "ForumsController").apiOnly();
    Route.get("users/forums", "UsersController.forumsByUser");
    Route.get("users/posts", "UsersController.postsByUser");
  }).middleware("auth:api");
}).prefix("api");

//......

上面的代码为不同的endpoints ,创建了我们的Routes ,可以访问我们的论坛应用。Route.group 将一个路由列表放入一个具有单一前缀的组。middleware 添加了一个脚本,将在请求传递到控制器之前执行。

到目前为止,我们已经为我们的论坛API创建了不同的端点。

请注意,resource 方法为我们的论坛API创建了所有我们需要的CRUD端点。

测试论坛的API

当使用任何HTTP客户端(如Hoppscotch)测试你的端点时,你可能会面临错误Cannot find module 'phc-argon2'

为了解决这个问题,请运行这个命令来安装软件包。

    npm install phc-argon2

如果我们在没有认证的情况下测试posts 端点,我们将面临这个错误。

Test API Error

如果我们使用/api/login 登录或通过/api/register 端点注册以获取我们的API Token,这个错误就会消除。

Test API Auth

在插入令牌作为Authorization header 的值后,我们可以访问受保护的端点。

Test API Header

按照下面的视频来测试API。

总结

在这篇文章中,我们介绍了如何在Adonis.js 5中建立一个RESTful API。我们创建了一个带有认证和授权的简单论坛API。