How to use content collection in Astro.
Tags: astro, markdown, typescript, tutorial
Published: 2023-02-06 16:38:50

In case you missed it, Astro launched 2.0 with a couple of exciting announcements, one of which is the new [Content collection API](https://docs.astro.build/en/guides/content-collections/) with Type-safety powered by [Zod](https://zod.dev/). 

In a previous post, I wrote a summary of [What's new in Astro 2.0](https://dev.to/obinnaspeaks/whats-new-in-astro-20-m42); you can check it out for more information.

This post will look at creating Content collections and how you can start building and sourcing content in your Astro application.

We will be doing so by building a simple newsletter application, exploring creating a collection, querying the collection in components and creating routes for each markdown file in the collection.

## Project setup 

To follow the project in the blogpost, you can fork my [content collection example project](https://codesandbox.io/p/github/Ekwuno/content-collecttion-api-example/main?file=%2FREADME.md) on Codesandbox. Create a fork, and you are good to go.

Pull the repo from [GitHub](https://github.com/Ekwuno/content-collecttion-api-example) and set up if you prefer to do this locally. 

The parts that are covered is the `Content/`, `Pages/newsletter` folders. The file structure looks like and t

```
/
├── public/
│   └── favicon.svg
├── src/
│   ├── components/
│   │   └── Card.astro
│   ├── content/
│   │   └── newsletter
│   │       ├── post-1.md
│   │       └── post-2.md
|   ├── config.ts
│   ├── layouts/
│   │   └── Layout.astro
│   └── pages/
│       ├── newsletter
│       │   ├── [slug].astro
│       └── index.astro
└── package.json
```

## Setting up  Content Collections in Astro

A content collection is a group of `.mdx` or `.md` files that are created under the `src/content` folder in Astro. The  content files are type-safe and allow you to group your content in a schema and type-safe way. Once you have a collection, you can start querying your content using Astro’s built-in Content APIs. 

### Define collection schema

First, create a `config.ts` file in the `src/content` folder, which is the config file where you define the "collections" for your project. We describe the frontmatter schema in this file and use Zod to define types.

From the code block below, you can notice that we define the schema with some types. Doing so ensures that when we create new `.md` or `.mdx` files, the frontmatter is type-safe, and we get the benefits of Typescript in markdown. 

```ts
import { z, defineCollection } from "astro:content";

// Define a collection of newsletter posts
const newsletterCollection = defineCollection({
// Define the schema 
	schema: z.object({
		title: z.string().max(100),
		date: z.date(),
		categories: z.array(z.string()),
		summary: z.string(),
		image: z.string().optional(), //Image can be optional
	})
});

// Export
export const collections = {
	//  collectionName: collection
	newsletter: newsletterCollection,
};

```

### Creating Markdown content in Astro 

Since we are creating content collections, the markdown files must follow a "collection" type format. So in the `src/content` folder, create a `newsletter` folder this is where we add markdown files and inside those files add the frontmatter, following the schema format that is was defined. 

Under the newsletter folder create a `post-1.md` and then define the frontmatter with respect to the schema 

```md
---
title: "Newsletter Post 1"
date: 2019-01-01T00:00:00.000Z
categories: [Newsletter, News]
summary: "This is the first newsletter post."
---
# Newsletter Post 1

// Content 
```
> The frontmatter is type-safe, so if you add any field that isn't defined in the schema, you will get a prompt to fix the error. Thankfully Astro has very [helpful error messages to prompt](https://dev.to/obinnaspeaks/whats-new-in-astro-20-m42). Also, notice how no image is defined because, in the schema, it is marked as optional. 

## Importing Content collections to components 

Now that you have defined the schema and have your first post up. You can pull your content data using the `getCollection` from `astro:content`. 

Then pass your collection name into the `getCollection` function. Doing so will give you an array that you can map through and display in a component. 

> You might need to restart your dev server after importing so that astro:content can be accessed. 

```ts
import { getCollection } from 'astro:content';
const newsletters = await getCollection('newsletter');

// ...

{ newsletters.map((newsletter) => {
	return (
		<Card
		 href={`/newsletter/${newsletter.slug}`}
		 title={newsletter.data.title}
		 body={newsletter.data.summary}/>
		)
	})
}
```

Notice that the `href` for the newsletter cards opens up a path `/newsletter/${newsletter.slug}`. This is because, in Astro, all pages are created under the pages folder so, we create a `newsletter` folder under pages to make routes for each post. 

> You can make multiple routes for each collection that you have.   

## Generate Pages for Content collections 

If you are familiar with creating dynamic routes, they are often depicted with `[]` and have an identifier to create the routes based on the identifier. 
For example, it can be making each route based on the id:`[name.id]` or on the folder's name `[Professions.name]`. 

To create a new page for each of the newsletter entries under the Pages folder, create `newsletters/[slug].astro`; this will create a new page for each post. 

Of course, you can also [define custom slugs](https://docs.astro.build/en/guides/content-collections/#defining-custom-slugs) for your files, but we will stick with the generated ones.

First, get the collection; you will use the `getCollection` function again.

```js 
import { getCollection } from "astro:content";
``` 

Then, because we want to create paths from these collections at build time, we make a function `getStaticPaths`. In this function, we will pass the newsletter collection into `getCollection` and await the response. 

This response is then mapped out to create a slug for each post at build time, the params Object shows what file is  rendered, as seen below: 

```js

// This function gets called at build time and generates the paths from the content folder
export async function getStaticPaths() {
    // Define the colection you are creating pages for
    const allNewsletters = await getCollection("newsletter");

    return allNewsletters.map((newsletter) => {
        return {
            params: {slug: newsletter.slug},
    // 
            props:{newsletter},
        };
    });
}
```

### Access props and rendering markdown for Content collections.
 
Now that we have defined the newsletter, we would need to access the props in this particular component, and we can do that with `Astro.props` to typecast this even further add a 
`{CollectionEntry<>}` type and pass in `newsletter` as seen below: 

```js
---
import {CollectionEntry, getCollection } from "astro:content";
//.. 

// Access the props in this component 
const {newsletter} = Astro.props as {newsletter: CollectionEntry<"newsletter">};
const {Content} = await newsletter.render();
---
```
To get the actual content from the `post-1.md` file, call the `render` function pass in the Content. 

We can now render the content like so:

```js
<main>
    <h1>{newsletter.data.title}</h1>
    <p>Category: {newsletter.data.categories.join(',')}</p>
    <Content />
    <p>
    <a href="/">Back to previous newsletters</a>
</p>
</main>
```

## Learn more

Astro's content collection API allows for so flexibility and type-safety when it comes to handling data. There is also a guide for [migrating from File-based routing](https://docs.astro.build/en/guides/content-collections/#migrating-from-file-based-routing) if you want to update your projects. 

To get started with this example project, you can create a fork of my [Content-collection API](https://codesandbox.io/p/github/Ekwuno/content-collecttion-api-example/main?file=%2FREADME.md) project on CodeSandbox. Happy coding 👋🏾