用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。
- 为你的认证输入
UserModel。 - 然后按
Y键,为它创建一个迁移。 - 接下来,选择
Database作为你的提供者。 - 现在再按
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 up 和public 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);
}
}
}
上面的代码分别创建了Post 或Forum 模型,并使用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 端点,我们将面临这个错误。

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

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

按照下面的视频来测试API。
总结
在这篇文章中,我们介绍了如何在Adonis.js 5中建立一个RESTful API。我们创建了一个带有认证和授权的简单论坛API。