使用 Next.js 和 Encore.ts 构建个人博客平台

2025-06-08

使用 Next.js 和 Encore.ts 构建个人博客平台

在本教程中,我们将构建一个实用且有趣的项目:个人博客平台。我们将重点介绍如何使用Encore.ts的 REST API 构建后端

我将逐步指导您从零开始,使用Encore Cloud的免费托管平台部署后端服务。我们将使用 PostgreSQL 作为数据库来存储博客数据。

激动吗?快来开始吧!

Encore 是什么?

图像

Encore是一个用于构建可扩展分布式系统的开源后端框架。它是一款开发者友好的工具,凭借其高性能 API 框架,可以轻松构建健壮、类型安全的应用程序。无论您喜欢 TypeScript 还是 Go,Encore 都支持。

Encore 还内置了各种开发工具,无论您是在开发小型个人项目还是大型后端系统,都能轻松上手。对于注重简洁性、性能和可扩展性的开发者来说,Encore 是一个绝佳的选择。

先决条件

您无需成为 Encore 专家即可学习本教程。但由于我们正在构建 REST API,如果您对 REST API 的工作原理有基本的了解,将会很有帮助。

诸如常见方法:GET、POST、PUT、DELETE 等在我们进行时将会派上用场。

如果你之前使用过 Node.js 或 Express,哪怕只是一点点,也是加分项。了解如何创建服务器路由或设置基本服务器,将有助于你在学习过程中串联起各个环节。

在开始之前,请确保您已安装最新版本的 Node.js。如果您还没有下载,请访问Node.js 网站并下载。下载完成后,我们就可以开始了。

图像

在开始之前,还有一件事:Encore CLI。访问 encore.dev,您将找到安装它所需的所有信息。如果您使用的是 Windows、macOS 或 Linux 系统,请按照您电脑的说明进行操作。

您也可以复制并粘贴以下命令直接安装:

# Install Encore on macOS
brew install encoredev/tap/encore

# Install Encore on Windows (PowerShell)
iwr https://encore.dev/install.ps1 | iex

# Install Encore on Linux
curl -L https://encore.dev/install.sh | bash
Enter fullscreen mode Exit fullscreen mode

现在你已经安装好了所有东西,让我们检查一下 Node.js 和 Encore 是否在你的机器上设置好了。就像你开始一个新项目时,需要确保所有工具都已到位一样。

检查节点版本的方法如下:

node -v
Enter fullscreen mode Exit fullscreen mode

这应该会显示你安装的 Node.js 版本。如果弹出版本号,就可以开始了。

我们来看一下安可版本:

encore version
Enter fullscreen mode Exit fullscreen mode

如果 Encore 安装正确,它也会显示其版本。

看到两个版本号了吗?太好了!一切就绪,我们准备继续下一步。如果没有,请仔细检查安装步骤,我们会处理。

图像

设置项目

现在让我们创建 Encore 项目。只需运行命令,它就会引导您完成设置 Encore 项目的过程。

以下是创建 Encore 项目的命令:

encore app create
Enter fullscreen mode Exit fullscreen mode

运行此程序时,Encore 会要求您为项目选择一种语言。在本教程中,我们将使用 TypeScript ,它是构建健壮且可扩展 API 的绝佳选择。

使用箭头键选择 TypeScript,然后按 Enter

图像

运行encore app create命令时,Encore 会提供一些模板供您选择。这些模板就像预先构建的蓝图,每个模板都可以根据您正在构建的项目类型帮助您入门。

但在本教程中,我们将保持简单并从头开始。我们将选择 “空应用” 模板。

为什么?因为从头开始构建是了解一切运作方式的最佳方式。

所以,继续选择 “空应用” 模板。Encore 将为您提供一个干净的空项目。

一旦您这样做,Encore 将为您生成一个干净、最小的项目结构。

没有额外的代码,没有预先构建的功能,只有一块等待您的想法的空白画布。

图像

现在到了有趣的部分,为你的项目命名。在本教程中,我将项目命名为 “blogs”,但你可以随意选择一个与你产生共鸣的名称。

图片描述

按下 Enter 键后,Encore 就会立即开始运行。它会开始下载 “空应用” 模板,并在后台设置你的项目。

您将看到终端文件中的一系列活动正在创建、依赖项正在安装,并且所有内容都整齐地组织成一个干净的项目结构。

设置完成后,Encore 可能会提示您立即运行项目。它会建议一个类似于 encore run “启动”的命令。

不过,等等,我们现在还不打算运行它。相反,我们将切换到你的代码编辑器,在那里打开项目,探索其结构,然后从那里运行它。

在我们构建和调整事物时,这是与代码保持同步的更好方法。

图像

现在,让我们在代码编辑器中打开我们的项目。我还想向你展示另一个功能,它将使你的开发工作变得更加轻松。

相信我,你一定会喜欢的。快来体验吧!

图像

当你在代码编辑器中打开项目时,它简洁得令人耳目一新。没有杂乱,也没有令人眼花缭乱的依赖项列表。如果你仔细查看package.json,你会发现只有一个主要依赖项:encore.dev

就是这样。除此之外,TypeScript 还作为开发依赖项静静地存在着,让一切保持整洁和专注。

在代码编辑器中打开终端并运行以下命令:

encore run
Enter fullscreen mode Exit fullscreen mode

只要按下Enter 键,神奇的事情就会发生。Encore 不仅会启动你的项目,还会自动打开浏览器,直接带你进入开发者面板

随着我们开始构建 API,我们将详细探索如何跟踪请求、调试错误等等。现在,请先花点时间感受一下它的无缝衔接。

Encore 正在承担繁重的工作,因此您可以专注于真正重要的事情:编写出色的代码。

让我们继续前进,还有更多的东西等待发掘!

图像

实现 API 端点

好了,让我们创建第一个 API 端点。我们将构建一个简单的 端点, 当你点击它时,/hello 它会响应 “Hello, world!” 。

让我们探索一下如何实现这一点。现在,让我们回到代码编辑器。

图像

在项目根目录下创建了一个名为hellohello.ts的目录,并在其中添加了一个名为 的文件。

这就是我们如何保持我们的项目有序的结构文件夹和文件,以便随着项目的发展而变得合理。

在文件中hello.ts,我api从 Encore 导入了函数。该函数是定义端点的核心。它主要包含两个部分:

  1. 第一个参数是一个对象,我们在其中定义了方法(例如 GET、POST 等)、路径(例如/hello)以及一个名为 的键expose: true。设置expose: true意味着此端点是公开可访问的,任何人都可以调用它。
  2. 一个异步函数,我们在这里编写端点返回的逻辑。在我们的例子中,它是一条简单的“Hello, world!”消息。

我希望这能让您对端点的结构以及使用它们时需要注意的事项有一个基本的了解。

现在我们已经掌握了基础知识,让我们换个方向,开始为我们的博客平台构建 REST API。接下来,事情会变得激动人心!

创建博客:

首先,在项目根目录下创建一个名为 blog 的新目录。在这个目录中,我们将添加一个名为 blog.ts 的文件。我们将在这里定义与博客功能相关的所有内容,以便在构建过程中保持整洁有序。

接下来,让我们定义博客的接口。由于我们正在构建一个简单的博客平台,所以我们会尽量简化。目前,我们只需要三个字段来创建一个博客:

  • 标题:博客的标题。
  • 内容:博客文章的主体。
  • 作者:撰写博客的人的姓名。

以下是我们在 TypeScript 中构建它的方式:

interface Blog {
  id: number;
  title: string;
  content: string;
  author: string;
  created_at: string;
  updated_at: string;
}

interface CreateBlogParams {
  title: string;
  content: string;
  author: string;
}
Enter fullscreen mode Exit fullscreen mode

现在,我们定义了一个createBlog函数,它本质上是一个帖子端点。它将获取博客信息来创建博客。

import { api } from "encore.dev/api";

interface Blog {
  id: number;
  title: string;
  content: string;
  author: string;
  created_at: string;
  updated_at: string;
}

interface CreateBlogParams {
  title: string;
  content: string;
  author: string;
}

// Create Blog
export const createBlog = api<CreateBlogParams, Blog>(
  {
    method: "POST",
    path: "/blogs",
    expose: true,
  },
  async ({ title, content, author }: CreateBlogParams) => {
    return { title, content, author } as Blog;
  }
);
Enter fullscreen mode Exit fullscreen mode

让我们使用 Encore 提供的开发者仪表板。你可以把它想象成增强版的 Postman,但功能更加丰富。打开仪表板后,你会看到一个下拉列表,其中包含你创建的所有端点。

例如,如果您创建了一个 createBlog 端点,它就会出现在列表中。您创建的每个端点都会显示在这里。

我们已经 createBlog 通过发送一些示例数据(如博客标题、内容和作者姓名)测试了我们的端点,它运行完美。

控制面板不仅显示您发送的请求,还显示您收到的回复 - 非常直观,使调试和测试更容易。

图像

在深入创建 API 之前,我们先花点时间设置一下数据库。毕竟,我们的博客平台需要一个地方来存储所有博客文章,对吧?

我们将使用 PostgreSQL 作为我们的数据库,Encore 使其集成和管理变得非常容易。

数据库设置完毕并准备就绪后,我们将回到 API 创建部分。这样,我们不仅可以创建博客,还可以无缝地存储和检索它们。

让我们先启动并运行数据库,然后我们将继续构建这些 API!

连接 PostgreSQL

为了连接 PostgreSQL,首先需要在机器上运行 Docker。Docker 非常适合创建和管理 PostgreSQL 之类的数据库,可以防止本地环境变得过于混乱。

不用担心,如果您没有 Docker,只需访问网站并下载适合您的操作系统的版本。

安装很简单,因此您将能够非常快速地创建 PostgreSQL 实例。

现在我们有了 Docker,我们将运行 PostgreSQL,然后连接到我们的 Encore 项目。首先,让我们获取 Docker!

图像

Docker 启动并运行后,您暂时无需对其进行任何操作。它只是在后台运行。

现在,让我们专注于为博客 API 创建 数据库模式 。为此,我们将使用 迁移文件。迁移就像数据库的版本控制,它们可以帮助您定义和更新数据库。

以下是我们的设置方法:

  1. 在blog 目录(我们之前创建的) 中 ,我们将添加另一个名为 的目录migrations。这是我们所有数据库迁移文件所在的位置。
  2. 在文件夹中 migrations ,我们将创建一个名为 的文件 1_create_blogs_table.up.sql。这个名字可能看起来有点具体,但它只是一个约定, 1 表示这是第一次迁移, .up.sql 意味着这个文件将处理创建或更新数据库模式。

在这个文件中,我们将定义表的模式 blogs 。

以下是一个例子:

CREATE TABLE blogs (
    id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    author TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Enter fullscreen mode Exit fullscreen mode

该架构包括:

  • id 随着每个新博客文章自动增加的列。 
  • title、 content和 author 列来存储博客详细信息。
  • created_at 用于跟踪博客创建时间的列。 

一旦此迁移文件到位,当我们运行项目时,Encore 将自动将其应用到数据库。

就这样,我们的数据库就可以存储博客文章了!

import { SQLDatabase } from "encore.dev/storage/sqldb";
Enter fullscreen mode Exit fullscreen mode

现在,让我们将代码连接到数据库。为此,我们需要  从 Encore 的 模块导入SQLDatabasesqldb  。这将使我们能够无缝地与 PostgreSQL 数据库交互。

导入后,我们将创建一个 数据库实例。由于我们的数据库名为 blogs,我们将使用它作为参考。

在数据库实例内部,我们还需要定义文件夹的路径 migrations 。这将告诉 Encore 在哪里可以找到我们之前创建的迁移文件。

代码如下:

const db = new SQLDatabase("blogs", { migrations: "./migrations" });
Enter fullscreen mode Exit fullscreen mode

通过此设置,Encore 确切地知道在哪里找到迁移文件,并会 blogs 在我们运行项目时自动将它们应用到数据库。

在我们构建博客平台时,这是一种管理数据库模式的干净而有效的方法。

import { api, APIError } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";

const db = new SQLDatabase("blogs", { migrations: "./migrations" });

interface Blog {
  id: number;
  title: string;
  content: string;
  author: string;
  created_at: string;
  updated_at: string;
}

interface CreateBlogParams {
  title: string;
  content: string;
  author: string;
}

// Create Blog
export const createBlog = api<CreateBlogParams, Blog>(
  {
    method: "POST",
    path: "/blogs",
    expose: true,
  },
  async ({ title, content, author }: CreateBlogParams): Promise<Blog> => {
    const row = await db.queryRow<Blog>`
      INSERT INTO blogs (title, content, author)
      VALUES (${title}, ${content}, ${author})
      RETURNING id, title, content, author, created_at, updated_at`;

    if (!row) {
      throw APIError.internal("Failed to create blog");
    }
    return row;
  }
);
Enter fullscreen mode Exit fullscreen mode

我们现在已使用 SQL 查询将 title、 content和 author 保存到 blogs 数据库中。

这是一个简单的 SQL 命令,用于处理数据库中的数据存储。重新运行应用程序后,您会注意到,在 Docker 中完成该过程后,会为数据库创建一个容器。

图像

太棒了,让我们在开发者面板上测试一下我们的端点。前往面板,你会看到你的端点列在那里。

只需点击它,提供所需数据,然后点击发送。无论成功还是错误,您都会立即看到响应。

这是一种快速简便的方法,可以确保一切正常运行。快来试试吧!

图像

现在,当我们检查响应时,我们可以看到 id、 created_at和 updated_at 字段。这意味着我们的数据已成功保存到数据库中。一切运行正常!

如果您想仔细查看表格和数据,可以使用 Encore DB Shell

它是一个方便的工具,可让您直接与数据库进行交互。

要打开 shell,只需在终端中运行以下命令:

encore db shell blogs
Enter fullscreen mode Exit fullscreen mode

这 blogs 是我们数据库的名称。运行此命令后,您将进入数据库 shell,在这里您可以浏览表、运行 SQL 查询,并查看数据库中存储的具体内容。

让我们尝试一下,看看它的内部情况!

图像

现在我们可以从表中查询。

图像

我们可以看到数据,虽然由于终端空间有限,数据有点拥挤。但重要的是它能正常工作!数据保存正确,我们已经验证过了。

至此,我们成功完成了创建博客的第一个端点。很酷吧?

让我们保持这种势头并取得更多成就!

阅读所有博客:

现在,让我们创建第二个端点,用于 从数据库中检索所有博客 。与创建 createBlog 端点的过程类似,我们将遵循类似的流程。

我们将定义端点,编写逻辑以从数据库获取数据并将其作为响应返回。

这一切都建立在我们已经学到的知识之上。让我们一起深入学习,完成它!

// Read All Blogs
export const getBlogs = api(
  {
    method: "GET",
    path: "/blogs",
    expose: true,
  },
  async (): Promise<{ blogs: Blog[] }> => {
    const rows = db.query`
          SELECT id, title, content, created_at
          FROM blogs
          ORDER BY created_at DESC
      `;

    const blogs: Blog[] = [];
    for await (const row of rows) {
      blogs.push({
        id: row.id,
        title: row.title,
        content: row.content,
        author: row.author,
        created_at: row.created_at,
        updated_at: row.updated_at,
      });
    }

    return { blogs };
  }
);
Enter fullscreen mode Exit fullscreen mode

让我们测试我们的新端点,看看所有博客是否正确显示。

前往 开发者仪表板, getAllBlogs 在下拉菜单中找到端点,然后点击发送。

如果一切设置正确,您应该会看到数据库中存储的所有博客的列表。

图像

通过 ID 获取博客:

现在,让我们创建一个端点, 通过 ID 获取单个博客。为此,我们将 id 在函数中将 作为参数。然后,我们将编写一个简单的 SQL 查询,从数据库中获取与给定 ID 匹配的博客 id

它很简单,只需对我们已经完成的内容进行一些小调整即可。让我们构建它,看看它是如何工作的!

// Read a Single Blog by ID
export const getBlogById = api<{ id: number }, Blog>(
  {
    method: "GET",
    path: "/blogs/:id",
    expose: true,
  },
  async ({ id }: { id: number }): Promise<Blog> => {
    const row = await db.queryRow<Blog>`
      SELECT * FROM blogs WHERE id = ${id}`;
    if (!row) {
      throw APIError.notFound("Blog not found");
    }
    return row;
  }
);
Enter fullscreen mode Exit fullscreen mode

让我们使用 开发者面板测试这个新端点。由于目前数据库中只有一个博客,因此我们将使用 来检查该博客 id: 1

前往仪表板,找到 getBlogById 端点,并将其 1 作为 ID 传递。点击“发送”,你会看到该博客的详细信息在响应中弹出。 

图像

通过 ID 更新博客:

通过 ID 更新博客非常简单。首先,我们定义一个 UpdateBlogParams 接口来构造我们期望的数据,例如 id、  title、 content和 author

然后,我们将使用 SQL 查询根据提供的内容更新数据库中的博客 id

如果给定的博客 id 不存在,我们将抛出一条错误消息让用户知道。


interface UpdateBlogParams {
  id: number;
  title: string;
  content: string;
}

// Update Blog
export const updateBlog = api<UpdateBlogParams, Blog>(
  {
    method: "PUT",
    path: "/blogs/:id",
    expose: true,
  },
  async ({ id, title, content }: UpdateBlogParams): Promise<Blog> => {
    const row = await db.queryRow<Blog>`
      UPDATE blogs
      SET title = ${title}, content = ${content}, updated_at = NOW()
      WHERE id = ${id}
      RETURNING id, title, content, author, created_at, updated_at`;

    if (!row) {
      throw APIError.notFound("Blog not found");
    }
    return row;
  }
);
Enter fullscreen mode Exit fullscreen mode

让我们测试一下,看看它是如何工作的。前往 开发者面板,找到 updateBlog 端点,然后传入 id 要更新的博客的 以及新的 title、 content或 author。 

图像

博客 1 的 title 和  已根据我们在请求正文中发送的数据进行了更新。content

如果您检查开发人员仪表板中的响应 ,您将看到反映在那里的更改。 

通过ID删除博客:

现在,让我们看看如何 通过 ID 删除博客。我们将 id 作为函数中的参数,并执行一个简单的 SQL 查询来从数据库中删除博客。

如果指定博客 id 存在,则会将其删除。如果不存在,我们会优雅地显示错误消息。

 

// Delete Blog
export const deleteBlog = api<{ id: number }, { message: string }>(
  {
    method: "DELETE",
    path: "/blogs/:id",
    expose: true,
  },
  async ({ id }: { id: number }): Promise<{ message: string }> => {
    const row = await db.queryRow<Blog>`
    SELECT * FROM blogs WHERE id = ${id}`;
    if (!row) {
      throw APIError.notFound("Blog not found");
    }

    await db.exec`DELETE FROM blogs WHERE id = ${id}`;

    return { message: "Blog deleted successfully" };
  }
);
Enter fullscreen mode Exit fullscreen mode

这是删除端点的测试版本 。请前往 开发者面板,找到该 deleteBlog 端点,并传入 id 要删除的博客的 。

图像

现在我们已经了解了  博客 API创建、 读取、 更新和 删除的所有CRUD 操作,您已经掌握了构建功能齐全的应用程序的基础。

从保存新博客到获取、更新甚至删除它,我们已经涵盖了基本内容。

希望本文能帮助您扎实理解如何使用 Encore 创建和管理应用程序。虽然还有很多内容需要探索,但这是一个很好的起点。

部署后端服务器:

Encore中的部署  是一件让人感觉几乎神奇的事情,它非常有趣而且超级简单。

只需 3 个命令,您就可以部署整个后端代码,包括数据库。

工作原理如下:

  1. 暂存您的更改

    git add .
    
  2.  使用以下消息提交您的更改:

    git commit -m "Your commit message here"
    
  3. 推至 Encore

    git push encore
    

就这样!Encore 负责构建应用程序、设置数据库以及将所有内容部署到云端的其余工作。

图像

现在从app.encore.dev打开您的应用程序

图像

这是您的 实时托管 URL!您现在可以使用此 URL 创建、 阅读、 更新和 删除 博客。

你猜怎么着?它已经托管在 暂存环境中了。

这有多酷?只需一分钟,您的应用程序即可启动并运行,并可供测试和使用。

Encore 让部署变得轻松无比。快来试用一下吧!

API 与前端集成

我创建了几个博客在前端展示。使用  部署后获得的 实时链接,我 向数据库中添加了4 个博客。

看到从构建后端到用真实数据填充一切过程如此快速,真是令人兴奋。

现在,这些博客已准备好显示在前端。

邮差

在这里,我创建了一个简单的 Next.js 前端应用程序 来演示我们的 API 如何与任何前端集成。

无论您构建的是时尚、现代的 UI 还是更简约的 UI,后端都已做好充分准备来处理它。

这就是结构良好的 API 的美妙之处,它可以与您能想象到的任何前端设计无缝协作。

"use client";
import axios from "axios";
import { useEffect, useState } from "react";

export default function BlogList() {
  const [blogs, setBlogs] = useState<
    {
      id: string;
      title: string;
      content: string;
      author: string;
      created_at: string;
    }[]
  >([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchBlogs = async () => {
      try {
        const response = await axios.get(
          "https://staging-blogs-nqei.encr.app/blogs"
        );
        console.log(response);
        setBlogs(response?.data?.blogs);
      } catch (err) {
        console.log(err);
        setError("Failed to load blogs. Please try again later.");
      } finally {
        setLoading(false);
      }
    };
    fetchBlogs();
  }, []);

  if (loading) {
    return (
      <div className="text-center mt-20 text-lg text-gray-500">
        Loading blogs...
      </div>
    );
  }

  if (error) {
    return (
      <div className="text-center mt-20 text-lg text-red-500">{error}</div>
    );
  }

  return (
    <div className="min-h-screen bg-gradient-to-b from-blue-50 to-blue-100 py-10 px-4 sm:px-10">
      <h1 className="text-4xl font-bold text-center text-gray-700 mb-10 drop-shadow-lg">
        {`Syket's`} Blog
      </h1>
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {blogs.map((blog) => (
          <div
            key={blog.id}
            className="bg-white shadow-md rounded-2xl p-6 hover:shadow-xl transition-shadow duration-300"
          >
            <h2 className="text-xl font-semibold text-gray-800 mb-2 truncate">
              {blog.title}
            </h2>
            <p className="text-gray-600 text-sm line-clamp-3">{blog.content}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

这就是我们 简洁的小博客页面!它虽然不花哨,但功能齐全。您可以看到我们之前创建的博客就显示在这里,这些博客是从后端 API 获取的。

这是后端和前端如何协调工作的一个很好的例子。

演示

结论

从项目一开始的设置到创建第一个 API、将其连接到 PostgreSQL 数据库,然后将其部署到 Encore 环境中,这对我们来说是一段不可思议的旅程。

当所有部件组装在一起时,有太多事情要做,太多值得欣赏。Encore 让设计健壮、类型安全且可快速扩展的应用程序变得简单。

如果您想更深入地了解,请务必查看文档。Encore 有很多功能。

此外,如果您喜欢该项目,请在 GitHub 上为 Encore 点赞

感谢您阅读到最后!

鏂囩珷鏉ユ簮锛�https://dev.to/encore/building-a-personal-blogging-platform-with-nextjs-and-encorets-44fh
PREV
Encore.ts — 冷启动速度比 NestJS 和 Fastify 快 17 倍
NEXT
我希望在开始认真开发之前了解的 5 件事(代码完美之旅)google-java-format