npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mongoose-slug-plugin

v2.1.0

Published

Slugs for Mongoose with history and i18n support (uses speakingurl by default, but you can use any slug library such as limax, slugify, mollusc, or slugme)

Downloads

2,049

Readme

mongoose-slug-plugin

build status code coverage code style styled with prettier made with lass license

Slugs for Mongoose with history and i18n support (uses speakingurl by default, but you can use any slug library such as limax, slugify, mollusc, or slugme)

Table of Contents

Install

npm:

npm install mongoose-slug-plugin

yarn:

yarn add mongoose-slug-plugin

Usage

Add the plugin to your project (it will automatically generate a slug when the document is validated based off the template string passed)

const mongooseSlugPlugin = require('mongoose-slug-plugin');
const mongoose = require('mongoose');

const BlogPost = new mongoose.Schema({
  title: String
});

BlogPost.plugin(mongooseSlugPlugin, { tmpl: '<%=title%>' });

module.exports = mongoose.model('BlogPost', BlogPost);

If you need to render some custom function in the template string for display purposes, such as outputting a formatted date with dayjs:

const dayjs = require('dayjs');

const mongooseSlugPlugin = require('mongoose-slug-plugin');
const mongoose = require('mongoose');

const BlogPost = new mongoose.Schema({
  title: { type: String, required: true, unique: true },
  posted_at: { type: Date, required: true }
});

BlogPost.plugin(mongooseSlugPlugin, {
  tmpl: "<%=title%>-<%=dayjs(posted_at).format('YYYY-MM-DD')%>",
  locals: { dayjs }
});

module.exports = mongoose.model('BlogPost', BlogPost);

If you're using Koa, here's an example showing how to lookup a slug or an archived slug and properly 301 redirect:

const Koa = require('koa');
const Router = require('koa-router');
const Boom = require('boom');

const BlogPosts = require('./blog-post');

const app = new Koa();
const router = new Router();

router.get('/blog/:slug', async (ctx, next) => {
  try {
    // lookup the blog post by the slug parameter
    const blogPost = await BlogPosts.findOne({ slug: ctx.params.slug });

    // if we found it then return early and render the blog post
    if (blogPost) return ctx.render('blog-post', { title: blogPost.title, blogPost });

    // check if the slug changed for the post we're trying to lookup
    blogPost = await BlogPosts.findOne({ slug_history: ctx.params.slug });

    // 301 permanent redirect to new blog post slug if it was found
    if (blogPost) return ctx.redirect(301, `/blog/${blogPost.slug}`);

    // if no blog post found then throw a nice 404 error
    // this assumes that you're using `koa-better-error-handler`
    // and also using `koa-404-handler`, but you don't necessarily need to
    // since koa automatically sets 404 status code if nothing found
    // <https://github.com/ladjs/koa-better-error-handler>
    // <https://github.com/ladjs/koa-404-handler>
    return next();

  } catch (err) {
    ctx.throw(err);
  }
});

app.use(router.routes());
app.listen(3000);

If you're using Express, here's an example showing how to lookup a slug or an archived slug and properly 301 redirect:

TODO

Note that you also have access to a static function on the model called getUniqueSlug.

This function accepts an _id and str argument. The _id being the ObjectID of the document and str being the slug you're searching for to ensure uniqueness.

This function is used internally by the plugin to recursively ensure uniqueness.

Static Methods

If you have to write a script to automatically set slugs across a collection, you can use the getUniqueSlug static method this package exposes on models.

For example, if you want to programmatically set all blog posts to have slugs, run this script (note that you should run the updates serially as the example shows to prevent slug conflicts):

const Promise = require('bluebird'); // exposes `Promise.each`

const BlogPost = require('../app/models/blog-post.js');

(async () => {
  const blogPosts = await BlogPost.find({}).exec();
  await Promise.each(blogPosts, async blogPost => {
    blogPost.slug = null;
    blogPost.slug = await BlogPost.getUniqueSlug(blogPost._id, blogPost.title);
    return blogPost.save();
  }));
})();

Options

Here are the default options passed to the plugin:

  • tmpl (String) - Required, this should be a lodash template string (e.g. <%=title%> to use the blog post title as the slug)
  • locals (Object) - Defaults to an empty object, but you can pass a custom object that will be inherited for use in the lodash template string (see above example for how you could use dayjs to render a document's date formatted in the slug)
  • alwaysUpdateSlug (Boolean) - Defaults to true (basically this will re-set the slug to the value it should be based off the template string every time the document is validated (or saved for instance due to pre-save hook in turn calling pre-validate in Mongoose)
  • errorMessage (String) - Defaults to Slug was missing or blank, this is a String that is returned for failed validation (note that it gets translated based off the this.locale field if it is set on the document (see Lad for more insight into how this works))
  • logger (Object) - defaults to console, but you might want to use Lad's logger
  • slugField (String) - defaults to slug, this is the field used for storing the slug for the document
  • historyField (String) - defaults to slug_history, this is the field used for storing a document's slug history
  • i18n (Object|Boolean) - defaults to false, but accepts a i18n object from Lad's i18n
  • slug (Function) - Defaults to speakingurl, but it is a function that converts a string into a slug (see below Custom Slug Libary examples)
  • slugOptions (Object) - An object of options to pass to the slug function when invoked as specified in options.slug

Slug Tips

If you're using the default slug library speakingurl, then you might want to pass the option slugOptions: { "'": '' } in order to fix contractions.

For example, if your title is "Jason's Blog Post", you probably want the slug to be "jasons-blog-post" as opposed to "jason-s-blog-post". This option will fix that.

See pid/speakingurl#105 for more information.

Slug Uniqueness

If a slug of "foo-bar" already exists, and if we are inserting a new document that also has a slug of "foo-bar", then this new slug will automatically become "foo-bar-1".

Custom Slug Library

If you don't want to use the library speakingurl for generating slugs (which this package uses by default), then you can pass a custom slug function:

limax example:

const limax = require('limax');

BlogPost.plugin(mongooseSlugPlugin, { tmpl: '<%=title%>', slug: limax });

slugify example:

const slugify = require('slugify');

BlogPost.plugin(mongooseSlugPlugin, { tmpl: '<%=title%>', slug: slugify });

mollusc example:

const slug = require('mollusc');

BlogPost.plugin(mongooseSlugPlugin, { tmpl: '<%=title%>', slug });

slugme example:

const slugme = require('slugme');

BlogPost.plugin(mongooseSlugPlugin, { tmpl: '<%=title%>', slug: slugme });

Background

I created this package despite knowing that other alternatives like it exist for these reasons:

  • No alternative supported i18n localization/translation out of the box
  • No alternative used the well-tested and SEO-friendly speakingurl package
  • No alternative allowed users to pass their own slug library
  • No alternative documented how to clearly do a 301 permanent redirect for archived slugs
  • No alternative allowed the field names to be customized
  • No alternative had decent tests written

Contributors

| Name | Website | | ---------------- | --------------------------------- | | Nick Baugh | http://niftylettuce.com/ | | shadowgate15 | https://github.com/shadowgate15 |

License

MIT © Nick Baugh