1.10.0 • Published 2 months ago

@mavvy/miniserver v1.10.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

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'
}
1.10.0

2 months ago

1.9.6

2 months ago

1.9.5

3 months ago

1.9.4

4 months ago

1.9.1

4 months ago

1.9.0

4 months ago

1.8.0

4 months ago

1.9.3

4 months ago

1.9.2

4 months ago

1.7.8

4 months ago

1.7.7

5 months ago

1.7.6

5 months ago

1.7.3

5 months ago

1.7.2

5 months ago

1.7.1

5 months ago

1.7.5

5 months ago

1.7.4

5 months ago

1.7.0

5 months ago

1.6.0

5 months ago

1.5.5

5 months ago

1.5.4

5 months ago

1.5.3

5 months ago

1.5.2

5 months ago

1.5.1

5 months ago

1.4.4

5 months ago

1.5.0

5 months ago

1.4.3

5 months ago

1.4.2

5 months ago

1.4.1

5 months ago

1.4.0

5 months ago

1.3.1

6 months ago

1.3.0

7 months ago

1.2.0

7 months ago

1.1.0

8 months ago

1.0.1

8 months ago

1.0.0

8 months ago