Skip to main content

Easy Restful APIs with the Django web framework.

Project description

PyPI-v PyPI-pyv PypI-djangov

Easy Restful APIs with the Django web framework.


Download through pip (virtualenv -p python3 .venv)

pip install django-express

Add it to your INSTALLED_APPS in

  # ...


Mount the auto-discovered services to any entry point (url) you want in urlpatterns

# proj/proj/

from django.conf.urls import url, include
from express import services

urlpatterns = [
    url(r'^api/v1/', include(services.urls)) # mount everything
    url(r'^app-name/api/v1/', include(services.url('app-name', ...))) # mount only those from specific app(s)

Please double check if your url() call here has the path argument ending with a trailing slash (e.g foo/bar/). This is required by the Django framework. You do not need to have this in your @url() decorator paths though.

Start serving apis

You can just start Django like normal, your apis will be automatically discovered and mounted.

./ runserver

Note that for other developers to use your apis, you need to bind on wildcard or public WAN/LAN accessable IP address specifically after runserver instead of leaving the param out to use the default localhost IP. If you are developing inside a VM (e.g through our Vagrant web dev vm) it is very important that you specify the VM’s IP or the wildcard IP after runserver so that you can use your host machine’s browser for accessing the apis through vm to host forwarded ports.

Also, use runserver with DEBUG=true in will automatically serve all the static/ sub-folders (and those added by STATICFILES_DIRS) from your apps. They are served like they were merged under the same uri set by STATIC_URL in your, so if you do not want files from different apps to override each other, put all your static assets (e.g *.js/html/css/png) in a sub-folder with the same name as your app inside each static/. Though STATIC_URL doesn’t have a default value, after running django-admin startproject your will set a default value /static/ to it so you could access files from the static/ sub-folders under http://domain:8000/static/<file path> with zero setup time.

If you are not using the runserver command for serving static assets and service apis during development, make sure you call ./ collectstatics and serve folder STATIC_ROOT on STATIC_URL, so that {% load static %} then {% static "images/hi.jpg" %} can work properly in your templates.

Adding RESTful services

Create apps in your Django project normally, this is to sub-divide your services by app name for better maintainability. Optional though.

./ startapp app_example
./ startapp another_app_with_services

Function as service api

Add a file in each app folder containing the service functions fn(req, res, *args, **kwargs) decorated with @service

# proj/app_example/
from express.decorators import service, methods, url

# /api/v1/absolute/url
# /api/v1/app_example/relative/abcd

@methods('GET', 'POST')
def abc(req, res, *args, **kwargs):
    res.json({'json': req.json, 'link:': reverse('')})

# /api/v1/app_example/efg

def efg(req, res, *args, **kwargs):
        'params': dict(req.params.lists()), # use {**req.params} in python 3.5+
        'form': dict(req.form.lists()), # use {**req.form} in python 3.5+
        'json': req.json,
        'mime': req['CONTENT_TYPE'],

# /api/v1/app_example/hij

def hij(req, res, *args, **kwargs):

# /api/v1/app_example/x

def x(req, res, *args, **kwargs):
    #res.text('Nothing but a test from {}'.format(__name__))
    res.text('<p>Agent: {}</p>'.format(req['HTTP_USER_AGENT']))
    res.html('<p>IP: {}</p>'.format(req['REMOTE_ADDR']))
    res.text('<p>Method: {}</p>'.format(req['REQUEST_METHOD']))

# /api/v1/app_example/relative/url/y-service/articles/2017/01/

def y1(req, res, y, m, *args, **kwargs):
        'data': 'Nothing but a test from {}.{}'.format(__name__, 'y1 - positional capture'),
        'text': 123,
        'year': y,
        'month': m,
    res.header('Hello~', 'World!') # header
    res.status(201) # status

# /api/v1/app_example/z

def z(req, res, *args, **kwargs):'db.sqlite3')

As you can see, you can still use regex captures in @url('..path..') if prefered. The captured group/named group will be passed normally to your service function as positional args and keyword args. However, You can NOT use both positioned and namged group captures in the same url!! Due to django implementation.

Important Note

Put @service as the inner-most decorator, other decorators don’t have this hard requirement on ordering here. You can still use all the decorators from the Django web framework like @permission_required or @login_required but make sure they are all above @service.

Argument APIs

The most important arguments to your service function would be the first two, namely req for request and res for response. Here are the available methods on these two objects.

req (ExpressRequest)
  • req.params[‘key’]
  • req.json
  • req.form
  • req.files[‘name’]
  • req.cookies[‘name’]
  • req[‘HTTP-HEADER’]/req.header(‘key’)
res (ExpressResponse)
  • res.redirect(‘url’)
  • res.render(req, ‘template’, context={})
  • res.html(‘str’)/text(‘str’)
  • res.json(dict)
  • res.file(‘path’)
  • res.attach(‘path’)/download(‘path’)
  • res.status(int)
  • res[‘HTTP_HEADER’]/res.header(‘key’, val)

Caveat: res.status() and res['HTTP_HEADER']/res.header() must be called after .render()/html()/text()/json()/file()/attach()/download() in your service function for new headers and status to be applied to the response.

Model generated service apis

Within the file, you can decorate any of your Model class directly for it to generate the apis around its CRUD database operations.

# proj/app_example/

class Device(models.Model):
    """docstring for Device"""
    sn = models.CharField(max_length=32)

This will mount 5 default service functions bound to different HTTP methods (POST/GET/PUT,PATCH/DELETE/HEAD) to url app_example/models/Device for its CRUD database operations and one more metadata operations.


For a function


Turn your fn(req, res, *args, **kwargs) function into a Restful service routine. Automatically detected if present in in any installed app.

  • Default path with services.urls: /<app>/<fn>
  • Default path with services.url(app, noprefix=True): /<fn>

You can change the mounting path by using the @url() decorator. You can also use django.urls.reverse() to get the mount point by name <namespace>:<app>.services.<fn> if you mount the ``services.url(s)`` with namespaced ``include()`` calls in ````.

Still, do not forget to mount everthing collected inside services.urls to a root url in the django See the Setup section above.

@methods(m1, m2, …)

Allowed HTTP request methods to the service. You can also use @safe to allow only GET and HEAD requests. You can use different @methods() on each service function with the same @url() path to reuse the same url.


Override basic service auto-path (/<app>/<fn>). No need to use r'..path..' here, what you put in path will be treated as raw string automatically. Feel free to put regex group captures. Just don’t mix named and annonymous capture groups in the url path, they won’t work together in django.

You can use multiple @url() on the same service function.


Setting CSRF token cookie on GET/HEAD requests to the service. Checks and rejects POST/PUT/PATCH/DELETE requests according to their csrf token + cookie pairs.

If you want an Ajax request to be guarded by django CSRF (django.middleware.csrf.CsrfViewMiddleware) you need to GET/HEAD the @csrf decorated service first to have your CSRF cookie (named csrftoken) set, then POST/PUT/PATCH/DELETE to it with real requests including either X-CSRFToken in header or csrfmiddlewaretoken in a hidden form <input> field. The header or hidden field value should match the value given by the cookie.

You can change the cookie and header names but NOT the hidden field name in the django

For a Model


Give a Model default RESTful apis to its CRUD operations.

  • Default path with services.urls: /<app>/<Model>
  • Default path wiht services.url(app, noprefix=True): /<Model>

You can change the mounting path by using the @url() decorator. You can also use django.urls.reverse() to get the mount point by name <namespace>:<app>.models.<fn>.

  • POST – create – {“payload”: {…data…}}
  • GET – read – ?pk= for single record, omit for all
  • PUT/PATCH – update – {“payload”: {“id”: “…”, …data…}}
  • DELETE – delete – ?pk= for target record, required
  • HEAD – meta – model name X-Django-App-Model and table count X-DB-Table-Count in reply headers

When using GET http request on a @serve(-ed) model, you can also specify params for filtering (by columns and Django ORM filter operations), sorting (by columns) and paging the returned result.

?sort=foo, -bar


When using Any http requests on a @serve(-ed) model, you can always use ?db=... to switch onto the specific database for served model apis to query and modify. The database names come from your DATABASES configure in

Still, do not forget to mount everthing collected inside services.urls to a root url in the django See the Setup section above.


Same as @serve but without csrf protection.

@methods(m1, m2, …)

Same as @methods for a service function.


Same as @url for a service function.

Database Backends


This is a dummy backend engine to use with MongoDB connections without the involvement of Django ORM. The purpose is to have your MongoDB settings in the and use django.db.connections['<your mongodb name>'] to start using MongoDB in your Django apps.


    'mongo': {
        'ENGINE': 'express.db.backends.mongodb',
        'HOST': '',
        'PORT': 27017,
        'NAME': 'testdb',
        'USER': '...',
        'PASSWORD': '...',
        'OPTIONS': {
            ...pymongo.MongoClient options...

Now you will have,

  • django.db.connections[‘testdb’].db - a pymongo db object;
  • django.db.connections[‘testdb’].collection(‘collection’=None) - a pymongo collection or all available collection names;
  • django.db.connections[‘testdb’].cursor(‘collection’, **kwargs) - a .find(kwargs) pymongo cursor;

After getting the above, you will have, * django.db.connections[‘testdb’].connection - a pymongo client;

Use .cursor() for search (GET) apis and .collection() for modify (POST/PUT/PATCH/DELETE) apis.


This engine works up to the point of creating the db connection and collection cursor, taking in DATABASES options from your; The ORM layer (migration, schema, transactions, save/delete()) will not work on database that has settings using this Engine.


Copyright 2017 Tim Lauv. Under the MIT License.

Project details

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Files for django-express, version 0.4.0
Filename, size File type Python version Upload date Hashes
Filename, size django_express-0.4.0-py3-none-any.whl (29.5 kB) File type Wheel Python version py3 Upload date Hashes View
Filename, size django-express-0.4.0.tar.gz (19.5 kB) File type Source Python version None Upload date Hashes View

Supported by

Pingdom Pingdom Monitoring Google Google Object Storage and Download Analytics Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page