Serve a RESTful API from any MongoDB database.
WORK IN PROGRESS
MangoREST aims to speed up serving RESTful APIs for CRUD apps utilizing MongoDB. It is built with Flask and pymongo, and is designed to simplify setting up the backend as much as possible but also giving room for extensibility.
Inspired by the giants RESTHEART (written in Java), and PostgREST (written in Haskell).
You may also view the auto-generated TOC using the button located at the upper left corner of this readme.
There is no hard-ruled step-by-step guide on how to get MangoREST up and running since it actually depends on the deployment strategy. Read the Installation and Deployment section for some deployment options. Remember that MangoREST is just a Flask app.
There are specific things that must be done though to start using MangoREST. One, is to provide configuration (see Configuration section for the config parameters). Also, the MangoREST user collection and at least one user must be created (see Authentication section).
MangoREST configuration is set using environment variables to determine the database information and ways on how to serve REST client requests. Reading from a .env
file is supported. Here is the complete list of configuration parameters:
Optional. Sets the context to where Flask is running in. Setting to development
will enable debug mode. Flask default setting is production
.
Reference: https://flask.palletsprojects.com/en/2.0.x/config/#environment-and-debug-features
Required. Used to specify how to load the application. Must be set to mangorest:app
. Please do change accordingly if customizing/extending MangoREST.
Reference: https://flask.palletsprojects.com/en/2.0.x/cli/
Required. This will be used to sign JWT access tokens needed for MangoREST's default auth using JWT. Read the Authentication section for the details. If summoning a terminal isn't your thing (woah), you may quickly generate a secret key using this web tool https://djecrety.ir/
Required. For the database connection.
Reference(1): https://docs.mongodb.com/manual/reference/connection-string/
Reference(2): https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient
Required. Name of the database to be exposed to REST clients. MangoREST only allows a single database to be specified. This database should already exist. The name itself will not be exposed.
Required. A sequence of resource_name:collection_name
pairs separated by commas. To avoid exposing the database's collection names, the resource_name
s will be used for the API endpoints. Map a resource_name
to the name of the collection that will be exposed to REST clients. These collections should already exist even if they are still empty.
If you don't care exposing ALL collections and their collection names, you can use an asterisk wildcard *
. The collection names themselves will be the resource_name
to be used for the API endpoints.
Optional. Name of the collection that will be used for storing data of MangoREST users created thru CLI. Default: mangorest_users
. This does not need to exist beforehand. MangoREST will create the collection if not yet existing. Read the Authentication section for more details.
Here is an example config taken from the .env.example
file in this repo:
FLASK_ENV=development
FLASK_APP=mangorest:app
JWT_SECRET_KEY=*1g$&3%an#x!+rogd@*iyhffs!a32575kd-)d*ajyr2s$kiuf!
MONGODB_URI=mongodb:https://userme:[email protected]:27017/mangorest
DATABASE=mangorest
COLLECTIONS=rockets:rocket_engines,vehicles:launch_vehicles
MANGO_USER_COLLECTION=users
MangoREST is just a Flask app. Therefore, ways to deploy Flask also apply to deploying MangoREST.
Reference: https://flask.palletsprojects.com/en/2.0.x/deploying/index.html
This section presents a few of the quick deployment options for MangoREST.
A quick and easy way to deploy and configure MangoREST as a Heroku app.
A quick and easy way to deploy and configure MangoREST as a Render app. Note that Render asks for Payment Information (if you haven't provided yet) when deploying thru One-Click button. But if deploying step-by-step thru the Render dashboard, no Payment Information will be asked.
WIP
WIP
This section discusses the auth sytem that comes with installing MangoREST. You can absolutely ditch this and implement your own.
First, please don't forget to provide a JWT_SECRET_KEY for the application. By default, authentication is required to send POST, PATCH, and DELETE to certain endpoints. The Flask-JWT-Extended extension is used to create and verify JWTs.
MangoREST provides a CLI that includes a command for creating users. Use mangorest createuser [username]
command. This will prompt for a password and password confirmation. If it succeeds, the username and password can now be used to authenticate and access protected routes. Send a POST to /login
endpoint to authenticate. This will respond with a JWT access token.
No REST API endpoint is available fo user registration. If needed though, this can be easily set up by using functions in the mangorest.auth module.
from mangorest import auth, config, services
@app.post("/register")
def register_user():
username = request.json.get("username", None)
password = request.json.get("password", None)
new_user_oid = auth.create_user(
users_collection=db[config.MANGO_USER_COLLECTION],
username=username,
password=password
)
parsed_oid = services.parse_object_id(new_user_id)
return jsonify({"username": username, **parsed_oid}), 201
As a summary for this section, here are the authentication-related endpoints:
Endpoint | Description |
---|---|
POST /login |
Send username and password; returns JWT token |
GET /me |
Provide JWT token in Authorization header; returns the username of the authenticated user |
POST /register |
If set up like the above, send username and password; returns the username and oid of the newly created user |
Routes provide GET, POST, PATCH, DELETE verbs. By default, only GET is available publicly, the rest require authentication. Read Authentication section for authentication details. Please note that all enpoints are within /api
which is automatically prepended by MangoREST.
A collection can be queried using the resource name mapped to it. For example, getting all the documents of the collection named rocket_engines
with a mapped resource name of rockets
:
GET /api/rockets
Results can be filtered by appending query string parameters:
GET /api/rockets?country=USA&thrust_to_weight_ratio=gte[int].50
This translates to "get the rockets with country of USA and has thrust to weight ratio of greater than or equal to 50".
Let's examine the query string above: country=USA&thrust_to_weight_ratio=gte[int].50
The country
param corresponds to the country field in the documents of rocket_engines collection. It has an equality condition of USA
. We then have an ampersand &
which is a way to logically conjoin multiple parameters. Now, the thrust_to_weight_ratio
param has a value with a special syntax: gte[int].50
.
gte corresponds to mongodb's $gte comparison query operator. Then, [int] is a type hint which instructs MangoREST that the value is an integer. Finally, we have a dot "." followed by the value 50.
All of mongodb's comparison and logical query operators are supported. Just remove the $
symbol when using them in the query string. But you may be asking more about the type hint thing. Pleae read the Type Hints section for the explanation.
The above query can also be done like this:
GET /api/rockets?and=(country.eq[str].USA,thrust_to_weight_ratio.gte[int].50)
This query string syntax instructs MangoREST to get the results by using mongodb's $and logical query operator. The expressions are separated by commas, and each expression is in the form of field_name.operator[type-hint].value
.
PROJECTION. To specify only a subset of fields of the documents to be returned, use the _projection
param.
GET /api/rockets?country=USA&_projection=name,cycle,burn_time,propellant
SORT. To sort the query results, use the _sort
param. The query below sorts by 2 fields.
GET /api/rockets?country=USA&_sort=(name:ascending),(burn_time:ascending)
LIMIT. To limit the number of results, use the _limit
param.
GET /api/rockets?country=USA&_sort=(name:ascending)&_limit=10
SKIP. To skip some documents (usually for pagination), use the _skip
param.
GET /api/rockets?country=USA&_skip=10
MangoREST currently only supports getting a single document thru its ObjectId.
GET /api/rockets/6195b0eb829a2784b4459a7f
Create and Update operations can only be done by authenticated users.
SINGLE INSERT. Using the rocket_engine collection above, create a new document as shown below. This responds with 201 CREATED
with the newly created document's _id
if succcessful.
POST /api/rockets
{"name":"RD-180", "country":"Russia", "thrust_to_weight_ratio": 78, "manufacturer": "NPO Energomash"}
BULK INSERTS. To insert multiple documents into a collection, pass an array of objects. This responds with 201 CREATED
with an array of the newly created documents' _id
s if succcessful.
POST /api/rockets
[
{
"name": "RD-360",
"country": "North Pole",
"thrust_to_weight_ratio": 150,
"manufacturer": "Energomasher",
},
{
"name": "RD-270",
"country": "Antarctica",
"thrust_to_weight_ratio": 110,
"manufacturer": "Energomasher",
},
]
SINGLE UPDATE. To update a document, use PATCH and specify the fields to be updated. This responds with 204 NO CONTENT
if succcessful.
PATCH /api/rockets/61a30c07032f56ecef3c845e
{
"manufacturer": "Energomasher Luna"
"thrust_to_weight_ratio": 150
}
BULK UPDATES. Use PATCH to the collection for updating multiple documents. Request body must specify update operators. The _projection, _sort, _limit, _skip
query params are ignored. Updating an unfiltered collection will be considered a fatal action and will respond with 403 FORBIDDEN
. Successful update will return 200 OK
.
PATCH /api/rockets?manufacturer=Energomasher
{
"$set": {
"country": "Moon"
}
}
SINGLE DELETE. To delete a single document, do as shown below. This responds with 204 NO CONTENT
if succcessful.
DELETE /api/rockets/61a30c07032f56ecef3c845e
BULK DELETES. Use DELETE to the collection for multiple deletes. The _projection, _sort, _limit, _skip
query params are ignored. Deleting an unfiltered collection will be considered a fatal action and will respond with 403 FORBIDDEN
. Successful delete will return 200 OK
.
DELETE /api/rockets?manufacturer=Energomasher
Type hints must be used in query strings to correctly filter the data to be returned. This is necessary since MangoREST currently does not generate or maintain a schema of the collection as a reference for the types. This section presents the ways type hints are used.
Queries where the field type is expected to be a string such as GET /api/rockets?country=Antarctica
does not need any type hinting. On the other hand, type hints are needed if a field type is expected to be other than a string. Such examples are:
GET /api/rockets?is_active=[bool].true
GET /api/rockets?thrust_to_weight_ratio=[int].70
Comparison query operators such as eq
, gt
, lte
can be used in the query string. However, type hint must be provided to correctly filter the data.
GET /api/rockets?thrust_to_weight_ratio=lt[int].70
GET /api/rockets?company=eq[str].Rocket+Lab&thrust_to_weight_ratio=gte[int].50
Some operators expect an array as the value such as in
and nin
. Array elements can be type hinted by:
GET /api/rockets?country=in[list-str].[North+Pole,Moon,Antarctica]
Logical query operators such as and
, or
can be used in the query string. Type hints must be provided.
GET /api/rockets?and=(thrust_to_weight_ratio.gt[int].70,country.eq[str].North+Pole,is_active.eq[bool].true)
To conclude this section, here are the type hints you can use in query strings.
Hint | Description/Python Type |
---|---|
int |
integer |
float |
float |
bool |
boolean |
str |
string |
date |
datetime.date |
time |
datetime.time |
datetime |
datetime.datetime |
timedelta |
datetime.timedelta |
list-int |
Array with integer elements |
list-float |
Array with float elements |
list-str |
Array with string elements |
list-date |
Array with datetime.date elements |
list-time |
Array with datetime.time elements |
list-datetime |
Array with datetime.datetime elements |
list-timedelta |
Array with datetime.timedelta elements |
The project uses poetry to package and manage dependencies. After activating a virtual environment, run:
(.venv)$ poetry install
A compose-devdb.yml is provided for developing and testing. It is recommended to use the param values in the .env.example file when developing and testing.
Start containers:
(.venv)$ docker-compose -f compose-devdb.yml up -d --build
Then, run tests:
(.venv)$ pytest
Please do linting:
bash scripts/lint-check.sh
And fix formatting:
bash scripts/format.sh