{"id":14975695,"url":"https://github.com/jack-carling/jeve","last_synced_at":"2026-03-05T06:02:18.275Z","repository":{"id":56741386,"uuid":"519271531","full_name":"jack-carling/jeve","owner":"jack-carling","description":"Quick way to REST","archived":false,"fork":false,"pushed_at":"2023-06-06T19:15:48.000Z","size":455,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-23T06:02:53.634Z","etag":null,"topics":["api","framework","mongoose","nodejs","rest"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jack-carling.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-29T15:56:43.000Z","updated_at":"2022-10-20T10:00:50.000Z","dependencies_parsed_at":"2024-11-12T05:47:31.545Z","dependency_job_id":null,"html_url":"https://github.com/jack-carling/jeve","commit_stats":{"total_commits":72,"total_committers":2,"mean_commits":36.0,"dds":0.06944444444444442,"last_synced_commit":"9d6687fd5dc37067eaeffe6e639bbc5fdc41a9e0"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jack-carling/jeve","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jack-carling%2Fjeve","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jack-carling%2Fjeve/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jack-carling%2Fjeve/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jack-carling%2Fjeve/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jack-carling","download_url":"https://codeload.github.com/jack-carling/jeve/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jack-carling%2Fjeve/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30111779,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T03:40:26.266Z","status":"ssl_error","status_checked_at":"2026-03-05T03:39:15.902Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","framework","mongoose","nodejs","rest"],"created_at":"2024-09-24T13:52:24.301Z","updated_at":"2026-03-05T06:02:18.214Z","avatar_url":"https://github.com/jack-carling.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\"https://user-images.githubusercontent.com/72305598/184533504-d38cfa2d-97b0-4dd1-95a2-d4a2a2dfb576.png\" alt=\"Jeve\" /\u003e\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fjack-carling%2Fjeve%2Fbadge%3Fref%3Dmain\u0026style=flat\" /\u003e\n\u003cimg src=\"https://img.shields.io/npm/dt/jeve\" /\u003e\n\u003cimg src=\"https://img.shields.io/npm/v/jeve\" /\u003e\n\u003cimg src=\"https://img.shields.io/npm/l/jeve\" /\u003e\n\u003c/p\u003e\n\u003cbr /\u003e\n\nJeve is a JavaScript framework for effortlessly building an API with a [self-written documentation](#self-written-documentation). Powered by [Express](https://expressjs.com/) and [Mongoose](https://mongoosejs.com/) for native [MongoDB](https://www.mongodb.com/) support. Inspired by [PyEve](https://python-eve.org/) for Python.\n\n## Table of contents\n\n-   [Install](#install)\n-   [Quick start](#quick-start)\n-   [Settings](#settings)\n-   [Schema](#schema)\n-   [Resource methods](#resource-methods)\n-   [Item methods](#item-methods)\n-   [Validations](#validations)\n-   [Params \u0026 queries](#params--queries)\n-   [Middleware](#middleware)\n-   [Custom routes](#custom-routes)\n-   [Accessing models](#accessing-models)\n-   [Self-written documentation](#self-written-documentation)\n\n## Install\n\n```\n$ npm install jeve\n```\n\n## Quick start\n\nJeve runs on port 5000 and uses `mongodb://localhost` as the default MongoDB connection string URI. Presuming that the database is running locally on the default port the following code is the bare minimum to get the API up and running:\n\n```javascript\nconst Jeve = require('jeve')\n\nconst settings = {\n    domain: {\n        people: {},\n    },\n}\n\nconst jeve = new Jeve(settings)\njeve.run()\n```\n\nThat's all it takes for the API to go live with an endpoint. We can now try to **GET** `/people`.\n\n```\n$ curl -i http://localhost:5000/people\nHTTP/1.1 204 No Content\n```\n\nWe're live but the endpoint returns no content since we haven't set up a schema. More about that later.\n\n## Settings\n\nIn order to change the default port and MongoDB URI simply add the keys to the root of the `settings` object. They can also be added in a `.env` file. In case values exists both in `.env` and in the root of the `settings` object, the root value will be superseded by dotenv.\n\n| key      | value  | default               | dotenv   |\n| -------- | ------ | --------------------- | -------- |\n| port     | number | `5000`                | PORT     |\n| database | string | `mongodb://localhost` | DATABASE |\n\nIf we would want to run Jeve on port 5100 instead for example:\n\n```javascript\nconst settings = {\n    domain: {\n        people: {},\n    },\n    port: 5100,\n}\n```\n\n## Schema\n\nJeve will automatically create Mongoose Models based on a `schema` object under each domain. Let's add name and age to the people domain as types string and number.\n\n```javascript\nconst settings = {\n    domain: {\n        people: {\n            schema: {\n                name: 'string',\n                age: 'number',\n            },\n        },\n    },\n}\n```\n\nTypes can be written directly as a string:\n\n```javascript\nname: 'string'\n```\n\nOr as an object with the `type` key:\n\n```javascript\nname: {\n  type: 'string',\n}\n```\n\nThe latter is needed if other validations like `required` or `unique` are going to be added. In our example, name is a required field.\n\n```javascript\nname: {\n  type: 'string',\n  required: true,\n}\n```\n\nThe following types are supported:\n\n-   string\n-   number\n-   date\n-   boolean\n-   objectid\n-   object\n-   array\n\nWe now have a `settings` object which looks like this:\n\n```javascript\nconst settings = {\n    domain: {\n        people: {\n            schema: {\n                name: {\n                    type: 'string',\n                    required: true,\n                },\n                age: 'number',\n            },\n        },\n    },\n}\n```\n\n## Resource methods\n\nOur endpoint `/people` allows HTTP method **GET** by default. In order to save a person to the database, we need to add the **POST** method to our domain resource.\n\nResource methods are added as strings in an array on the same root as our `schema` object. Valid HTTP methods are **GET** and **POST**.\n\nBefore adding this to our settings, let's try to **POST**.\n\n```\n$ curl -i -X POST http://localhost:5000/people\nHTTP/1.1 404 Not Found\n```\n\nAs expected, we get a 404 status code response. Add the following to the `people` object and try again:\n\n```javascript\npeople: {\n  resourceMethods: ['GET', 'POST'],\n  schema: { ... },\n}\n```\n\n```\n$ curl -i -X POST http://localhost:5000/people\nHTTP/1.1 400 Bad Request\n```\n\nThis time we get a 400 status code response instead along with a json error message since we sent an empty body request.\n\n```javascript\n{\n  \"_success\": false,\n  \"_issues\": [\n    {\n      \"name\": \"required field\"\n    }\n  ]\n}\n```\n\nIf we instead send a proper **POST** application/json with a name:\n\n```\ncurl -i -d '{\"name\":\"James Smith\"}' -H \"Content-Type: application/json\" -X POST http://localhost:5000/people\nHTTP/1.1 201 Created\n```\n\nNow our first successful **POST** was made!\n\n```javascript\n{\n  \"_success\": true,\n  \"_item\": {\n    \"_id\": \"62eebccf0c5aa6efc2d8ceed\",\n    \"name\": \"James Smith\",\n    \"_created\": \"2022-08-06T19:11:11.627Z\",\n    \"_updated\": \"2022-08-06T19:11:11.627Z\"\n  }\n}\n```\n\nBy default only **GET** methods are allowed unless an array of `resourceMethods` have been defined. If however, you'd like an endpoint only serving **POST** requests, simply add that as the single value to the array.\n\n## Item methods\n\nIn the previous example our request returned an item with an `_id`. If we wanted to access only this item in a **GET** request, we could add the `_id` as a parameter to the request: `/people/62eebccf0c5aa6efc2d8ceed`.\n\nBy default the only valid HTTP method is **GET**, however if we would want other methods to be allowed we simply add them to our `itemMethods` array in a similar way as the `resourceMethods`.\n\nThe main difference to think about is that resource methods take care of the domain itself, accessing `/people` in order to **GET** a list of documents or **POST** a new document. While item methods handle a mandatory parameter which is the `_id` of the document in order to **GET** that specific document or handle updates or deletions. Valid methods are:\n\n-   GET\n-   PUT\n-   PATCH\n-   DELETE\n\n## Validations\n\nSchema keys are actual field names and in case the value is an object instead of the `type` as a string the following validation rules are can be used:\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003etype\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003e\n      Field data type. Required in the schema and can be one of the following:\n      \u003cul\u003e\n        \u003cli\u003e\u003ccode\u003estring\u003c/code\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ccode\u003enumber\u003c/code\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ccode\u003edate\u003c/code\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ccode\u003eboolean\u003c/code\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ccode\u003eobjectid\u003c/code\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ccode\u003eobject\u003c/code\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ccode\u003earray\u003c/code\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003erequired\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eIf \u003ccode\u003etrue\u003c/code\u003e the field is mandatory on insertion.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003eunique\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eIf \u003ccode\u003etrue\u003c/code\u003e the value of the field must be unique within the collection.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003edefault\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003e\n      The default value for the field. When serving POST and PUT requests, missing fields will be assigned the\n      configured default values.\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003eminLength\u003c/code\u003e, \u003ccode\u003emaxLength\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eMinimum and maximum length allowed for \u003ccode\u003estring\u003c/code\u003e types.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003emin\u003c/code\u003e, \u003ccode\u003emax\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eMinimum and maximum values allowed for \u003ccode\u003enumber\u003c/code\u003e types.\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nAn example where name has to contain at least 2 characters and email must be unique within the collection:\n\n```javascript\n{ /* ... */\n  name: {\n    type: 'string',\n    required: true,\n    minLength: 2\n  },\n  email: {\n    type: 'string',\n    required: true,\n    unique: true,\n  }\n}\n```\n\n## Params \u0026 queries\n\nIf we make a new **GET** request to `/people`, we now get everything that's stored in the database.\n\n```\n$ curl -i http://localhost:5000/people\nHTTP/1.1 200 OK\n```\n\n```javascript\n{\n  \"_success\": true,\n  \"_items\": [\n    {\n      \"_id\": \"62eebccf0c5aa6efc2d8ceed\",\n      \"name\": \"James Smith\",\n      \"_created\": \"2022-08-06T19:11:11.627Z\",\n      \"_updated\": \"2022-08-06T19:11:11.627Z\"\n    }\n  ],\n  \"_meta\": {\n    \"total\": 1,\n    \"limit\": 10,\n    \"page\": 1,\n    \"pages\": 1\n  }\n}\n```\n\nIn our case the `_items` array only contains one (1) object, the one we just added. Responses are paginated by default and the `_meta` object contains information about the specific endpoint.\n\n| \\_meta |  description                              |\n| ------ | ----------------------------------------- |\n| total  | The total number of documents found       |\n| limit  | Max results per page                      |\n| page   | The page which the cursor is currently on |\n| pages  | The total number of pages                 |\n\nIf we imagined that we had 12 documents in the `/people` collection the `_meta` response would look something like this:\n\n```javascript\n{\n  \"_success\": true,\n  \"_items\": [ /* ... */ ],\n  \"_meta\": {\n    \"total\": 12,\n    \"limit\": 10,\n    \"page\": 1,\n    \"pages\": 2\n  }\n}\n```\n\nSince we know there's a second page, we can simply do a new **GET** with the page query:\n\n```\n$ curl -i \"http://localhost:5000/people?page=2\"\nHTTP/1.1 200 OK\n```\n\nIf we wanted more results per page:\n\n```\n$ curl -i \"http://localhost:5000/people?limit=20\"\nHTTP/1.1 200 OK\n```\n\nIf we're looking for a specific document the `_id` of that document needs to follow as a parameter, for example `/people/62eebccf0c5aa6efc2d8ceed`. This is the way **PATCH**, **PUT** and **DELETE** knows what document to handle as well.\n\nOther valid queries are `sort`, `where` and `select`.\n\nIf we wanted our result to be sorted by their creation date we could send the `/people?sort=_created` query as an example. Or if we wanted to reverse the search, simply add `-` before the key value: `/people?sort=-_created`.\n\nThe last two parameters accepts json-input, `where` will filter the request. If for example we only wanted a list of people older than 18 we could use the following query: `/people?where={\"age\":{\"$gte\": 18}}`. `select` will filter the documents, as in including specific fields or excluding others. If we for example weren't interested in the ages, we could exclude that field by specifying the key along with a `0`: `/people?select={\"age\":0}`.\n\n## Middleware\n\nEach domain accepts a `preHandler` function which will run before the request. Use cases range from authorization to catching data and manipulating the body. As an example, let's imagine we had a `boolean` value for the field `isAdult` in our schema. We're not sending this value in our request, but we want our middleware to catch it.\n\n```javascript\n{ /* ... */\n  people: {\n    resourceMethods: ['GET', 'POST'],\n    schema: {\n      age: 'number',\n      isAdult: 'boolean',\n    },\n    preHandler: checkIfAdult,\n  }\n}\n```\n\nIn our middleware function `checkIfAdult`, we would simply add the value to it. Don't forget to call with `next()`...\n\n```javascript\nfunction checkIfAdult(req, res, next) {\n    const age = req.body?.age\n    if (age) req.body.isAdult = age \u003e= 18\n    next()\n}\n```\n\n## Custom routes\n\nJeve supports custom routes:\n\n| method | function        |\n| ------ | --------------- |\n| GET    | `jeve.get()`    |\n| POST   | `jeve.post()`   |\n| PUT    | `jeve.put()`    |\n| PATCH  | `jeve.patch()`  |\n| DELETE | `jeve.delete()` |\n\nA simple example of a `/greeting` route that returns the text `Hello World!`:\n\n```javascript\njeve.get('/greeting', (req, res) =\u003e {\n    res.send('Hello World!')\n})\n```\n\nIf `greeting` exists in our `domain` object, the custom route will be skipped and not initialized due to conflict and a message will be shown in the console. However, if the path is deeper, for example `/greeting/swedish`, the custom route will be created.\n\n## Accessing Models\n\nEvery model that's dynamically created by Jeve is accessible from the `jeve.model()` function. If we for example wanted to access a model from a custom route and use any native Mongoose function with it:\n\n```javascript\njeve.get('/greeting/:id', async (req, res) =\u003e {\n    const { id } = req.params\n    const person = await jeve.model('people').findOne({ _id: id })\n    res.send(`Hello ${person.name}`)\n})\n```\n\n## Self-written documentation\n\nJeve will dynamically create it's own documentation and the UI is accessible at `/docs` in the browser. The documentation contains all available routes in the `settings` object and will show which resource and item methods they're accessible by. The accordion contains the `schema` object, an overview of keys and validations.\n\n![](https://user-images.githubusercontent.com/72305598/188952457-6b0a5e6b-03e5-4748-a372-a134293ea833.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjack-carling%2Fjeve","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjack-carling%2Fjeve","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjack-carling%2Fjeve/lists"}