astro-loader-goodreads v1.2.0
astro-loader-goodreads
Load Goodreads data for books in shelves/lists, user updates, and author blogs into Astro.
!NOTE
astro-loader-goodreads
uses the Astro Content Loader API to fetch data from Goodreads RSS feeds.See the Usage section below for instructions on how to use the package to load the fetched Goodreads data into Astro Content Collections.
Table of Contents
Features
- Load Bookshelves: Import your Goodreads shelves to showcase your reading list, books you've read, or want to read.
- User Updates: Display your latest Goodreads activity including reading status updates, reviews, and likes.
- Author Blogs: Fetch author blogs from Goodreads to display updates from your favorite authors.
- Astro Content Collections: Seamlessly integrates with Astro's content collection system for type-safe data access.
Community Examples
Below are some examples of websites that use astro-loader-goodreads
. If you wish to add your site to this list, open a pull request!
Site | Page | Description | Source |
---|---|---|---|
sadman.ca | sadman.ca/about | Books I'm currently reading and have recently finished. | → |
Installation
npm add astro-loader-goodreads
Usage
Loader Options
Property | Description | Required | Default |
---|---|---|---|
url | The URL of your Goodreads shelf, user, or author. | ✅ | - |
refreshIntervalDays | Number of days to cache data before fetching again from Goodreads. | ❌ | 0 |
When refreshIntervalDays
is set (e.g., to 7
for weekly updates), the loader will only fetch new data from Goodreads when that many days have passed since the last fetch.
feat: Add optional loader option refreshIntervalDays
. If not specified, no caching is done between builds (Astro's default data caching between page loads still applies).
Defining & Using Astro Content Collections
astro-loader-goodreads
supports loading Goodreads data from 3 types of urls:
- Shelves: Load books from a Goodreads shelf.
- User Updates: Load a Goodreads user's updates feed.
- Author Blogs: Load a Goodreads author's blog.
In your content.config.ts
or src/content/config.ts
file, you can define your content collections using each type of URL with the goodreadsLoader
function.
!NOTE For the full list of fields available for Astro content collections created using
astro-loader-goodreads
, see the Data Schema section below.
1. Goodreads Shelves
To load data for books from a Goodreads shelf, use the shelf's URL (e.g. https://www.goodreads.com/review/list/152185079-sadman-hossain?shelf=currently-reading). astro-loader-goodreads
will convert it to the correct RSS feed URL automatically.
!IMPORTANT The RSS feed for a Goodreads shelf only includes the last 100 books added to that shelf. This means that if there are more than 100 books in a shelf,
astro-loader-goodreads
will not be able to retrieve them all.You can, however, create multiple shelves (e.g. read-2025, read-2026, etc.) and then create a content collection for each shelf to get around this limitation.
// src/content/config.ts
import { defineCollection } from "astro:content";
import { goodreadsLoader } from "astro-loader-goodreads";
const currentlyReading = defineCollection({
loader: goodreadsLoader({
url: "https://www.goodreads.com/review/list/152185079-sadman-hossain?shelf=currently-reading",
refreshIntervalDays: 7, // optional parameter; set to only fetch new data once per week
}),
});
export const collections = { currentlyReading };
---
// src/pages/reading.astro
import { getCollection } from "astro:content";
const books = await getCollection("currentlyReading");
---
<h1>Books I'm Currently Reading</h1>
<div class="book-grid">
{books.map((book) => (
<div class="book-card">
<img src={book.data.book_large_image_url} alt={`Cover of ${book.data.title}`} />
<h2>{book.data.title}</h2>
<p class="author">by {book.data.author_name}</p>
{book.data.user_rating > 0 && (
<p class="rating">My rating: {book.data.user_rating}/5</p>
)}
<a href={book.data.link} target="_blank" rel="noopener noreferrer">
View on Goodreads
</a>
</div>
))}
</div>
<style>
.book-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 2rem;
}
.book-card {
border: 1px solid #eee;
border-radius: 0.5rem;
padding: 1rem;
text-align: center;
}
.book-card img {
max-width: 100%;
height: auto;
}
</style>
2. Goodreads User Updates
To load a Goodreads user's updates feed, use the user's profile URL (e.g. https://www.goodreads.com/user/show/152185079-sadman-hossain). astro-loader-goodreads
will convert it to the correct RSS feed URL automatically.
!IMPORTANT The RSS feed for a Goodreads user's updates only includes the last 10 updates by that user. This means that
astro-loader-goodreads
cannot retrieve more than 10 updates for any single user.
// src/content/config.ts
import { defineCollection } from "astro:content";
import { goodreadsLoader } from "astro-loader-goodreads";
const userUpdates = defineCollection({
loader: goodreadsLoader({
url: "https://www.goodreads.com/user/show/152185079-sadman-hossain",
}),
});
export const collections = { userUpdates };
---
// src/pages/activity.astro
import { getCollection } from "astro:content";
const updates = await getCollection("userUpdates");
// Sort updates by publication date (newest first)
const sortedUpdates = updates.sort((a, b) =>
new Date(b.data.pubDate).getTime() - new Date(a.data.pubDate).getTime()
);
---
<h1>My Goodreads Activity</h1>
<div class="activity-feed">
{sortedUpdates.map((update) => {
const itemData = update.data.itemData;
return (
<div class="activity-item">
<p class="date">{new Date(update.data.pubDate).toLocaleDateString()}</p>
{itemData?.type === "ReadStatus" && (
<div class="read-status">
<img
src={itemData.bookImgUrl}
alt={`Cover of ${itemData.bookTitle}`}
width="50"
/>
<div>
<p>
<strong>{itemData.readingStatus}</strong>
<a href={itemData.bookUrl}>{itemData.bookTitle}</a>
by {itemData.bookAuthor}
</p>
</div>
</div>
)}
{itemData?.type === "Review" && (
<div class="review">
<img
src={itemData.bookImgUrl}
alt={`Cover of ${itemData.bookTitle}`}
width="50"
/>
<div>
<p>
Rated <strong>{itemData.rating} stars</strong> for
<a href={itemData.bookUrl}>{itemData.bookTitle}</a>
by {itemData.bookAuthor}
</p>
</div>
</div>
)}
{itemData?.type === "CommentReview" && (
<div class="comment-review">
<div>
<p>
Commented on <a href={itemData.reviewUrl}>{itemData.reviewUser}'s review</a> of
<a href={itemData.bookUrl}>{itemData.bookTitle}</a>:
</p>
<blockquote>"{itemData.comment}"</blockquote>
</div>
</div>
)}
{/* Add additional item types as needed */}
</div>
);
})}
</div>
3. Goodreads Author Blogs
To load Goodreads author blog posts, use the author's URL (e.g. https://www.goodreads.com/author/show/3389.Stephen_King). astro-loader-goodreads
will append the necessary parameters to fetch the blog RSS feed automatically.
// src/content/config.ts
import { defineCollection } from "astro:content";
import { goodreadsLoader } from "astro-loader-goodreads";
const authorBlog = defineCollection({
loader: goodreadsLoader({
url: "https://www.goodreads.com/author/show/3389.Stephen_King",
}),
});
export const collections = { authorBlog };
---
// src/pages/author-updates.astro
import { getCollection } from "astro:content";
const posts = await getCollection("authorBlog");
---
<h1>Latest Updates from Stephen Kingn</h1>
<div class="blog-posts">
{posts.map((post) => (
<article class="blog-post">
<h2>{post.data.title}</h2>
<p class="date">Published: {new Date(post.data.pubDate).toLocaleDateString()}</p>
{post.data.content && (
<div class="content" set:html={post.data.content} />
)}
<a href={post.data.link}>Read on Goodreads</a>
</article>
))}
</div>
Data Schema
Overview
The astro-loader-goodreads package provides three main schemas:
BookSchema
- For books from Goodreads shelvesUserUpdateSchema
- For user updates (with various activity types)AuthorBlogSchema
- For author blog posts
1. BookSchema
This schema is used when loading data from a Goodreads shelf.
export const BookSchema = z.object({
id: z.coerce.string(),
title: z.coerce.string(),
guid: z.string(),
pubDate: z.string(),
link: z.string(),
book_id: z.coerce.string(),
book_image_url: z.string(),
book_small_image_url: z.string(),
book_medium_image_url: z.string(),
book_large_image_url: z.string(),
book_description: z.string(),
num_pages: z.string().optional(),
author_name: z.string(),
isbn: z.coerce.string(),
user_name: z.string(),
user_rating: z.number(),
user_read_at: z.string(),
user_date_added: z.string(),
user_date_created: z.string(),
user_shelves: z.string().optional(),
user_review: z.string().optional(),
average_rating: z.number(),
book_published: z.coerce.string(),
});
Book Fields
Field | Description |
---|---|
id | Unique identifier for the book |
title | Book title |
guid | Global unique identifier for this entry |
pubDate | Publication date of this entry in the feed |
link | URL to the book's Goodreads page |
book_id | Goodreads ID for the book |
book_image_url | URL to the book cover image |
book_small_image_url | URL to small version of book cover (50×75 px) |
book_medium_image_url | URL to medium version of book cover (65×98 px) |
book_large_image_url | URL to large version of book cover (316×475 px) |
book_description | Description/synopsis of the book |
num_pages | Number of pages in the book (optional) |
author_name | Name of the book's author |
isbn | International Standard Book Number |
user_name | Username of who added the book to their shelf |
user_rating | Rating given by the user (0-5) |
user_read_at | Date when the user finished reading the book |
user_date_added | Date when the book was added to the user's shelf |
user_date_created | Date when this entry was created |
user_shelves | List of shelves the user assigned to this book (optional) |
user_review | User's review of the book (optional) |
average_rating | Average rating on Goodreads |
book_published | Book's original publication date |
2. UserUpdateSchema
This schema is used when loading data from a Goodreads user's updates feed.
export const UserUpdateSchema = z.object({
id: z.string(),
title: z.string(),
link: z.string().optional(),
description: z.string().optional(),
pubDate: z.string(),
itemType: z.string().optional(),
itemData: ItemDataSchema.optional()
});
UserUpdateSchema
Item Types
The itemData
field contains a discriminated union based on the type
field:
AuthorFollowing
When a user follows an author:
{
type: "AuthorFollowing",
followId: string,
userUrl: string,
authorId: string
}
UserStatus
When a user reports progress on a book:
{
type: "UserStatus",
userUrl: string,
percentRead: string,
bookUrl: string,
bookTitle: string,
bookAuthor: string,
bookImgUrl: string
}
ReadStatus
When a user changes their reading status:
{
type: "ReadStatus",
userUrl: string,
readingStatus: string, // 'started reading', 'wants to read', or 'finished reading'
bookUrl: string,
bookTitle: string,
bookAuthor: string,
bookImgUrl: string
}
Review
When a user posts a review:
{
type: "Review",
userUrl: string,
rating: number,
bookUrl: string,
bookTitle: string,
bookAuthor: string,
bookImgUrl: string
}
LikeReview
When a user likes someone's review:
{
type: "LikeReview",
userUrl: string,
reviewUrl: string,
reviewUser: string,
bookUrl: string,
bookTitle: string,
bookImgUrl: string
}
LikeReadStatus
When a user likes someone's read status:
{
type: "LikeReadStatus",
userUrl: string,
readStatusUser: string,
readStatusUserImgUrl: string,
readStatus: string,
bookUrl: string,
bookTitle: string
}
CommentStatus
When a user comments on a status:
{
type: "CommentStatus",
userUrl: string,
statusUrl: string,
statusUser: string,
comment: string
}
CommentReview
When a user comments on a review:
{
type: "CommentReview",
userUrl: string,
reviewUrl: string,
reviewUser: string,
bookUrl: string,
bookTitle: string,
bookAuthor: string,
comment: string
}
3. AuthorBlogSchema
This schema is used when loading data from a Goodreads author's blog.
export const AuthorBlogSchema = z.object({
id: z.string(),
title: z.string(),
link: z.string(),
description: z.string(),
pubDate: z.string(),
author: z.string().optional(),
content: z.string().optional(),
});
AuthorBlogSchema
Fields
Field | Description |
---|---|
id | Unique identifier for the blog post |
title | Blog post title |
link | URL to the blog post |
description | Raw HTML description of the blog post |
pubDate | Publication date |
author | Author's name (if available) |
content | Main content of the blog post (if available) |
License
astro-loader-goodreads
is MIT licensed.
Built with ♥ by @sadmanca!