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

@mavvy/miniserver

v1.10.0

Published

A Minimalist All-in-one Nodejs Framework

Downloads

29

Readme

MiniServer - A Minimalist Nodejs HTTP Server

Creating a Nodejs server should be easy. Keep It Super Simple right?

Features

  • Filename-based api: Inspired by Next.js API routes, allowing you to organize your APIs based on file names.
  • Multipart Form File Upload: Support for file uploads via multipart forms without the need for additional middleware.
  • Built-in Mongoose Support: Easily integrate with MongoDB using Mongoose with utility functions.
  • Microservices Integration: Seamlessly integrate with other MiniServers to build microservices architecture. see Miscroservice Example
  • Authentication Support: Authentication capabilities using jsonwebtoken, while offering flexibility for other authentication methods.

Preview

Check out the code snippet below for a quick overview:

import fs from 'fs';

export const handler = ({ input }) => {
  fs.writeFileSync('img.png', input.myImage.fileData);
}

In this example, the form data is available in the input parameter, allowing direct access within the function. No imports or middleware needed, just plain simplicity!

Handling Mongoose CRUD Operations

export const handler = async ({ db }) => db.create();

This code snippet adds data to the MongoDB database. The handler automatically detects input data, simplifying the development process.

How about microservices?

// mainService/src/someApi.ts
export const handler = async ({ services, input }) => {
  const product = await services.products.getItem(input.id);
  return product.data;
}

// productService/src/getItem.ts
export const handler = async ({ db, input }) => db.findById(input);

Example

see examples directory example

Getting Started

Automatic Install

npx create-miniserver my-project

Manual Install

npm install @mavvy/miniserver

install typescript

npm install typescript --save-dev

package.json

Set type to module and add start script

{
  "type": "module",
  "scripts": {
    "start": "miniserver start"
  }
}

sample tsconfig.json file

{
  "compilerOptions": {
    "lib": ["es2020"],
    "target": "es2020",
    "module": "esnext",
    "moduleResolution": "node",
    "esModuleInterop": true
  }
}

.env

Set optional environment variables, such as PORT.

export PORT = 3333

Creating your API

// src/greet.ts

export async function handler() {
  return 'hello world!';
}

Usage

Perform client-side requests using fetch:

fetch('http://localhost:3000/api', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    "handler": "greet",
  })
})

returns:

{
  "data": "hello world!"
}

Everything is a POST Request - Simple yet Effective!

All requests are POSTs, eliminating concerns about query parameters, routes, payload, URL encoding, etc.

Handling inputs

// addProduct.ts

export async function handler({input}) {
  return input;
}

Call the API

fetch('http://localhost:3000/api', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "handler": "addProduct",
      "input": {
        "name": "foo"
      }
    })
})

returns:

{
  "data": {
    "name": "foo"
  }
}

Handling multipart-form

Just add the handler field, all the other fields will be automatically injected to the input object

the file value contains: |key|description| |---|-----------| |filename|original file name| |encoding|file ecoding, eg: 7bit| |mimeType|eg: image/png| |fileData|buffer string of the file|

file input example

If you have a multipart-form data like this:

handler = myFileUpload
myImage = my_image.png

You should have a src/myfileUpload.ts file

// src/myFileUpload.ts
import fs from 'fs';

export const handler = ({ input }) => {
  console.log(input)

  fs.writeFileSync('img.png', input.myImage.fileData);

  return 'ok'
}

it should log:

{
  "input": {
    "myImage": {
      "filename": "my_image.png",
      "encoding": "7bit",
      "mimeType": "image/png",
      "fileData": <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 07 54 00 00 00 a0 08 06 00 00 00 bb 53
 5e 4d 00 00 0c 6c 69 43 43 50 49 43 43 20 50 72 6f 66 69 ... 47402 more bytes>
    }
  }
}

With Mongoose

Mongoose is supported out of the box, just add .env and _schema.ts and you are good to go

env

export MONGO_URI = your mongo uri

_schema.ts

// src/_schema.ts


export default [{
  name: 'Product',
  fields: {
    name: String,
    productType: String
  }
}]

usage

// src/addProduct.ts

export const handler = async ({ db }) => db.create();

The above code snippet adds a Product to the collection with the given input from the post request. The handler knows the model from the name of the file which is addProduct, because it gets the last uppercase first letter word and it also has the input data so you don't have to add it explicitly. simple.

If you want to use the short version, but the handler name's substring didn't match the model name, you can export a model from your handler:

// src/addProduct2.ts

export const model = 'Product';

export const handler = async ({db }) => db.create();

NOTE: handler names or file names can be in kebab case.

Also, if you want to use the long version:

// src/addProduct.ts

export const handler = async ({ mongoose, input }) => {
  const model = mongoose.model('Product');
  const res = await model.create(input);

  return res;
}

Services

Creating a microservice using miniserver is super easy.

Usage

create the services config

// _config.ts

export const SERVICES = {
  user: {
    url: 'http://localhost:3000/api',
    methods: ['users', 'userById']
  }
}

Take a look at the example above. The key user is the label for your service, you can name it anything. The value url is the url of your miniserver service. And the methods are the api handler that is exposed and that will be used by the service consumer.

Here is the sample usage on your handler

// src/getAllUsers.ts

export const handler = async ({ services }) => {
  const users = await services.user.users();

  return users.data;
}
// src/getUserById.ts

export const handler = async ({ services, input }) => {
 const user = await services.user.userById(input);

 return user.data;
}

See the Docker Example to see it in action.

Services Context

Shared context among your services

Example:

Product Service

export const handler = async ({ services, context }) => {
  context.add('myData', 'foo');
  const result = await services.review.getData();

  return result.data;
}

Review Service

// src/getData.ts
export const handler = ({ context }) => {
  const ctx = context.data();

  return `Data = ${ctx.myData}`; // returns "Data = foo"
}

Advanced Configuration - _config.ts

PRE_INIT hook

Runs the PRE_INIT hook before initiating the server

// src/_config.ts

import http from 'node:http';

export const PRE_INIT = async (server: http.Server) => {
  console.log('Hello World!');
}

Change api root uri

The default api root uri is /api , to change it - go to your .env file and set the ROOT_URI

// src/_config.ts

export const ROOT_URI = '/foo';

Disable CORS

// src/_config.ts

export const DISABLE_CORS = true;

Authentication

Authentication via jwt parsing on headers authorization bearer. Jwt utilities and db is available as parameters. It is required to return an object with fields of id, which is the user id. and the role.

The role could be any string value, it depends on your preferences.

Sample code:

// src/_config.ts
export const AUTH_HANDLER = async ({ req, jwt, db }) => {
  const token = req.headers.authorization?.split(' ')[1];
  const jwtData = await jwt.verifyAndDecode(token);

  if (!jwtData) {
    throw new Error('unauthorized');
  }

  const user = await db
    .model('User')
    .findOne({ clientToken: jwtData.payload.clientToken });

  if (!user) {
    throw new Error('unauthorized_token');
  }

  return { id: user.id, role: user.role };
};

Usage on the handler:

export the roles. which is an array of strings, those roels will have access to the api.

export const roles = ['ADMIN'];

export const handler = async () => {
  return 'authorized api'
}