Skip to content

Drupal 和 Astro

Drupal 是一个开源内容管理工具。

¥Drupal is an open-source content management tool.

¥Prerequisites

首先,你需要具备以下条件:

¥To get started, you will need to have the following:

  1. Astro 项目 - 如果你还没有 Astro 项目,我们的 安装指南 将立即帮助你启动并运行。

  2. Drupal 站点 - 如果你尚未设置 Drupal 站点,你可以遵循官方指南 安装 Drupal

将 Drupal 与 Astro 集成

标题部分 将 Drupal 与 Astro 集成

¥Integrating Drupal with Astro

安装 JSON:API Drupal 模块

标题部分 安装 JSON:API Drupal 模块

¥Installing the JSON:API Drupal module

为了能够从 Drupal 获取内容,你需要启用 Drupal JSON:API 模块

¥To be able to get content from Drupal you need to enable the Drupal JSON:API module.

  1. 通过“管理”管理菜单导航到“扩展”页面 admin/modules
  2. 找到 JSON:API 模块并选中它旁边的框
  3. 单击“安装”以安装新模块

现在,你可以通过 JSON:API 向你的 Drupal 应用发出 GET 请求。

¥Now you can make GET requests to your Drupal application through JSON:API.

.env 中添加 Drupal URL

标题部分 在 .env 中添加 Drupal URL

¥Adding the Drupal URL in .env

要将你的 Drupal URL 添加到 Astro,请在项目的根目录中创建一个 .env 文件(如果尚不存在)并添加以下变量:

¥To add your Drupal URL to Astro, create a .env file in the root of your project (if one does not already exist) and add the following variable:

.env
DRUPAL_BASE_URL="https://drupal.ddev.site/"

重新启动开发服务器以在你的 Astro 项目中使用此环境变量。

¥Restart the dev server to use this environment variable in your Astro project.

¥Setting up Credentials

默认情况下,Drupal JSON:API 端点可用于外部数据获取请求,而无需身份验证。这允许你在没有凭据的情况下为你的 Astro 项目获取数据,但不允许用户修改你的数据或站点设置。

¥By default, the Drupal JSON:API endpoint is accessible for external data-fetching requests without requiring authentication. This allows you to fetch data for your Astro project without credentials but it does not permit users to modify your data or site settings.

但是,如果你希望限制访问并要求身份验证,Drupal 提供了 几种身份验证方法,包括:

¥However, if you wish to restrict access and require authentication, Drupal provides several authentication methods including:

你可以将凭据添加到你的 .env 文件中。

¥You can add your credentials to your .env file.

.env
DRUPAL_BASIC_USERNAME="editor"
DRUPAL_BASIC_PASSWORD="editor"
DRUPAL_JWT_TOKEN="abc123"
...
Read more about using environment variables and .env files in Astro.

你的根目录现在应包含这些新文件:

¥Your root directory should now include this new files:

  • .env
  • astro.config.mjs
  • package.json

¥Installing dependencies

JSON:API 请求和响应通常很复杂且嵌套很深。为了简化使用它们,你可以使用两个 npm 包来简化请求和响应处理:

¥JSON:API requests and responses can often be complex and deeply nested. To simplify working with them, you can use two npm packages that streamline both the requests and the handling of responses:

  • JSONA:JSON API v1.0 规范序列化器和反序列化器,可在服务器和浏览器中使用。

  • Drupal JSON-API Params:此模块提供了一个辅助类来创建所需的查询。在此过程中,它还会尽可能使用简短形式来优化查询。

终端窗口
npm install jsona drupal-jsonapi-params

¥Fetching data from Drupal

你的内容是从 JSON:API URL 获取的。

¥Your content is fetched from a JSON:API URL.

Drupal JSON:API URL 结构

标题部分 Drupal JSON:API URL 结构

¥Drupal JSON:API URL structure

基本 URL 结构为:/jsonapi/{entity_type_id}/{bundle_id}

¥The basic URL structure is: /jsonapi/{entity_type_id}/{bundle_id}

URL 始终以 jsonapi 为前缀。

¥The URL is always prefixed by jsonapi.

  • entity_type_id 指的是实体类型,例如节点、块、用户等。

  • bundle_id 指的是实体包。对于 Node 实体类型,包可以是文章。

  • 在这种情况下,要获取所有文章的列表,URL 将为 [DRUPAL_BASE_URL]/jsonapi/node/article

要检索单个实体,URL 结构将为 /jsonapi/{entity_type_id}/{bundle_id}/{uuid},其中 uuid 是实体的 UUID。例如,获取特定文章的 URL 将采用 /jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e 格式。

¥To retrieve an individual entity, the URL structure will be /jsonapi/{entity_type_id}/{bundle_id}/{uuid}, where the uuid is the UUID of the entity. For example the URL to get a specific article will be of the form /jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e.

¥Retrieving only certain fields

通过将查询字符串字段添加到请求中仅检索特定字段。

¥Retrieve only certain field by adding the Query String field to the request.

GET:/jsonapi/{entity_type_id}/{bundle_id}?field[entity_type]=field_list

示例:

¥Examples:

  • /jsonapi/node/article?fields[node--article]=title,created

  • /jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e?fields[node--article]=title,created,body

¥Filtering

通过添加过滤器查询字符串为你的请求添加过滤器。

¥Add a filter to your request by adding the filter Query String.

最简单、最常见的过滤器是键值过滤器:

¥The simplest, most common filter is a key-value filter:

GET:/jsonapi/{entity_type_id}/{bundle_id}?filter[field_name]=value&filter[field_other]=value

示例:

¥Examples:

  • /jsonapi/node/article?filter[title]=Testing JSON:API&filter[status]=1

  • /jsonapi/node/article/2ee9f0ef-1b25-4bbe-a00f-8649c68b1f7e?fields[node--article]=title&filter[title]=Testing JSON:API

你可以在 JSON:API 文档 中找到更多查询选项。

¥You can find more query options in the JSON:API Documentation.

¥Building a Drupal query

Astro 组件可以使用 drupal-jsonapi-params 包从你的 Drupal 站点获取数据以构建查询。

¥Astro components can fetch data from your Drupal site by using drupal-jsonapi-params package to build the query.

以下示例显示了一个查询 “article” 内容类型的组件,该组件具有用于标题的文本字段和用于内容的富文本字段:

¥The following example shows a component with a query for an “article” content type that has a text field for a title and a rich text field for content:

---
import {Jsona} from "jsona";
import {DrupalJsonApiParams} from "drupal-jsonapi-params";
import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
// Get the Drupal base URL
export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;
// Generate the JSON:API Query. Get all title and body from published articles.
const params: DrupalJsonApiParams = new DrupalJsonApiParams();
params.addFields("node--article", [
"title",
"body",
])
.addFilter("status", "1");
// Generates the query string.
const path: string = params.getQueryString();
const url: string = baseUrl + '/jsonapi/node/article?' + path;
// Get the articles
const request: Response = await fetch(url);
const json: string | TJsonApiBody = await request.json();
// Initiate Jsona.
const dataFormatter: Jsona = new Jsona();
// Deserialise the response.
const articles = dataFormatter.deserialize(json);
---
<body>
{articles?.length ? articles.map((article: any) => (
<section>
<h2>{article.title}</h2>
<article set:html={article.body.value}></article>
</section>
)): <div><h1>No Content found</h1></div> }
</body>

你可以在 Drupal JSON:API 文档 中找到更多查询选项

¥You can find more querying options in the Drupal JSON:API Documentation

使用 Astro 和 Drupal 创建博客

标题部分 使用 Astro 和 Drupal 创建博客

¥Making a blog with Astro and Drupal

通过上述设置,你现在可以创建一个使用 Drupal 作为 CMS 的博客。

¥With the setup above, you are now able to create a blog that uses Drupal as the CMS.

¥Prerequisites

  1. 安装了 JSONADrupal JSON-API Params 的 Astro 项目。

  2. 至少有一个条目的 Drupal 站点 - 对于本教程,我们建议从具有标准安装的新 Drupal 站点开始。

    在 Drupal 站点的内容部分,单击添加按钮创建一个新条目。然后,选择文章并填写字段:

    • 标题:My first article for Astro!

    • 别名:/articles/first-article-for astro

    • 描述:This is my first Astro article! Let's see what it will look like!

    单击“保存”以创建你的第一篇文章。随意添加任意数量的文章。

¥Displaying a list of articles

  1. Create src/types.ts if it does not already exist and add two new interfaces called DrupalNode and Path with the following code. These interfaces will match the fields of your article content type in Drupal and the Path fields. You will use it to type your article entries response.

    src/types.ts
    export interface Path {
    alias: string
    pid: number
    langcode: string
    }
    export interface DrupalNode extends Record<string, any> {
    id: string
    type: string
    langcode: string
    status: boolean
    drupal_internal__nid: number
    drupal_internal__vid: number
    changed: string
    created: string
    title: string
    default_langcode: boolean
    sticky: boolean
    path: Path
    }

    Your src directory should now include the new file:

    • .env
    • astro.config.mjs
    • package.json
    • Directorysrc/
      • types.ts
  2. Create a new file called drupal.ts under src/api and add the following code:

    src/api/drupal.ts
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {DrupalNode} from "../types.ts";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    // Get the Drupal Base Url.
    export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;

    This will import the required libraries such as Jsona to deserialize the response, DrupalJsonApiParams to format the request url and the Node and Jsona types. It will also get the baseUrl from the .env file.

    Your src/api directory should now include the new file:

    • .env
    • astro.config.mjs
    • package.json
    • Directorysrc/
      • Directoryapi/
        • drupal.ts
      • types.ts
  3. In that same file, create the fetchUrl function to make the fetch request and deserialize the response.

    src/api/drupal.ts
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {DrupalNode} from "../types.ts";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    // Get the Drupal Base Url.
    export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;
    /**
    * Fetch url from Drupal.
    * * @param url
    * * @return Promise<TJsonaModel | TJsonaModel[]> as Promise<any>
    */
    export const fetchUrl = async (url: string): Promise<any> => {
    const request: Response = await fetch(url);
    const json: string | TJsonApiBody = await request.json();
    const dataFormatter: Jsona = new Jsona();
    return dataFormatter.deserialize(json);
    }
  4. Create the getArticles() function to get all published articles.

    src/api/drupal.ts
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {DrupalNode} from "../types.ts";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    // Get the Drupal Base Url.
    export const baseUrl: string = import.meta.env.DRUPAL_BASE_URL;
    /**
    * Fetch url from Drupal.
    * * @param url
    * * @return Promise<TJsonaModel | TJsonaModel[]> as Promise<any>
    */
    export const fetchUrl = async (url: string): Promise<any> => {
    const request: Response = await fetch(url);
    const json: string | TJsonApiBody = await request.json();
    const dataFormatter: Jsona = new Jsona();
    return dataFormatter.deserialize(json);
    }
    /**
    * Get all published articles.
    * * @return Promise<DrupalNode[]>
    */
    export const getArticles = async (): Promise<DrupalNode[]> => {
    const params: DrupalJsonApiParams = new DrupalJsonApiParams();
    params
    .addFields("node--article", [
    "title",
    "path",
    "body",
    "created",
    ])
    .addFilter("status", "1");
    const path: string = params.getQueryString();
    return await fetchUrl(baseUrl + '/jsonapi/node/article?' + path);
    }

    Now you can use the function getArticles() in an .astro component to get all published articles with data for each title, body, path and creation date.

  5. Go to the Astro page where you will fetch data from Drupal. The following example creates an articles landing page at src/pages/articles/index.astro.

    Import the necessary dependencies and fetch all the entries from Drupal with a content type of article using getArticles() while passing the DrupalNode interface to type your response.

    src/pages/articles/index.astro
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    import type { DrupalNode } from "../../types";
    import {getArticles} from "../../api/drupal";
    // Get all published articles.
    const articles = await getArticles();
    ---

    This fetch call using getArticles() will return a typed array of entries that can be used in your page template.

    Your src/pages/ directory should now include the new file, if you used the same page file:

    • .env
    • astro.config.mjs
    • package.json
    • Directorysrc/
      • Directoryapi/
        • drupal.ts
      • Directorypages/
        • Directoryarticles/
          • index.astro
      • types.ts
  6. Add content to your page, such as a title. Use articles.map() to show your Drupal entries as line items in a list.

    src/pages/articles/index.astro
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    import type { DrupalNode } from "../types";
    import {getArticles} from "../api/drupal";
    // Get all published articles.
    const articles = await getArticles();
    ---
    <html lang="en">
    <head>
    <title>My news site</title>
    </head>
    <body>
    <h1>My news site</h1>
    <ul>
    {articles.map((article: DrupalNode) => (
    <li>
    <a href={article.path.alias.replace("internal:en/", "")}>
    <h2>{article.title}</h2>
    <p>Published on {article.created}</p>
    </a>
    </li>
    ))}
    </ul>
    </body>
    </html>

¥Generating individual blog posts

使用与上述相同的方法从 Drupal 获取数据,但这一次,在将为每篇文章创建唯一页面路由的页面上。

¥Use the same method to fetch your data from Drupal as above, but this time, on a page that will create a unique page route for each article.

此示例使用 Astro 的默认静态模式,并使用 getStaticPaths() 函数创建 动态路由页面文件。该函数将在构建时被调用以生成成为页面的路径列表。

¥This example uses Astro’s default static mode, and creates a dynamic routing page file with the getStaticPaths() function. This function will be called at build time to generate the list of paths that become pages.

  1. Create a new file src/pages/articles/[path].astro and import the DrupalNode interface and getArticle() from src/api/drupal.ts. Fetch your data inside a getStaticPaths() function to create routes for your blog.

    src/pages/articles/[path].astro
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    import type { DrupalNode } from "../../types";
    import {getArticles} from "../../api/drupal";
    // Get all published articles.
    export async function getStaticPaths() {
    const articles = await getArticles();
    }
    ---

    Your src/pages/articles directory should now include the new file:

    • .env
    • astro.config.mjs
    • package.json
    • Directorysrc/
      • Directoryapi/
        • drupal.ts
      • Directorypages/
        • Directoryarticles/
          • index.astro
          • [path].astro
      • types.ts
  2. In the same file, map each Drupal entry to an object with a params and props property. The params property will be used to generate the URL of the page and the props values will be passed to the page component as props.

    src/pages/articles/[path].astro
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    import type { DrupalNode } from "../../types";
    import {getArticles} from "../../api/drupal";
    // Get all published articles.
    export async function getStaticPaths() {
    const articles = await getArticles();
    return articles.map((article: DrupalNode) => {
    return {
    params: {
    // Choose `path` to match the `[path]` routing value
    path: article.path.alias.split('/')[2]
    },
    props: {
    title: article.title,
    body: article.body,
    date: new Date(article.created).toLocaleDateString('en-EN', {
    day: "numeric",
    month: "long",
    year: "numeric"
    })
    }
    }
    });
    }
    ---

    The property inside params must match the name of the dynamic route. Since the filename is [path].astro, the property name passed to params must be path.

    In our example, the props object passes three properties to the page:

    • title: a string, representing the title of your post.
    • body: a string, representing the content of your entry.
    • created: a timestamp, based on your file creation date.
  3. Use the page props to display your blog post.

    src/pages/articles/[path].astro
    ---
    import {Jsona} from "jsona";
    import {DrupalJsonApiParams} from "drupal-jsonapi-params";
    import type {TJsonApiBody} from "jsona/lib/JsonaTypes";
    import type { DrupalNode } from "../../types";
    import {getArticles} from "../../api/drupal";
    // Get all published articles.
    export async function getStaticPaths() {
    const articles = await getArticles();
    return articles.map((article: DrupalNode) => {
    return {
    params: {
    path: article.path.alias.split('/')[2]
    },
    props: {
    title: article.title,
    body: article.body,
    date: new Date(article.created).toLocaleDateString('en-EN', {
    day: "numeric",
    month: "long",
    year: "numeric"
    })
    }
    }
    });
    }
    const {title, created, body} = Astro.props;
    ---
    <html lang="en">
    <head>
    <title>{title}</title>
    </head>
    <body>
    <h1>{title}</h1>
    <time>{date}</time>
    <article set:html={body.value} />
    </body>
    </html>
  4. Navigate to your dev server preview and click on one of your posts to make sure your dynamic route is working.

¥Publishing your site

要部署你的网站,请访问我们的 部署指南 并按照你首选托管提供商的说明进行操作。

¥To deploy your website, visit our deployment guides and follow the instructions for your preferred hosting provider.

更多 CMS 指南

Astro 中文网 - 粤ICP备13048890号