Allow dictionaries return values as JSON
diff --git a/CHANGES.rst b/CHANGES.rst
index 2a6641c65d..b3c092b7fa 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -52,6 +52,10 @@ Unreleased
 -   Add an ``--extra-files`` option to the ``flask run`` CLI command to
     specify extra files that will trigger the reloader on change.
     :issue:`2897`
+-   Allow returning a dictionary from a view function. Similar to how
+    returning a string will produce a ``text/html`` response, returning
+    a dict will call ``jsonify`` to produce a ``application/json``
+    response. :pr:`3111`
 
 .. _#2935: https://github.com/pallets/flask/issues/2935
 .. _#2957: https://github.com/pallets/flask/issues/2957
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index 47200a13bc..8dad6f3c35 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -679,23 +679,26 @@ See :ref:`error-handlers` for more details.
 About Responses
 ---------------
 
-The return value from a view function is automatically converted into a
-response object for you.  If the return value is a string it's converted
-into a response object with the string as response body, a ``200 OK``
-status code and a :mimetype:`text/html` mimetype.
-The logic that Flask applies to converting return values into
-response objects is as follows:
+The return value from a view function is automatically converted into
+a response object for you. If the return value is a string it's
+converted into a response object with the string as response body, a
+``200 OK`` status code and a :mimetype:`text/html` mimetype. If the
+return value is a dict, :func:`jsonify` is called to produce a response.
+The logic that Flask applies to converting return values into response
+objects is as follows:
 
 1.  If a response object of the correct type is returned it's directly
     returned from the view.
-2.  If it's a string, a response object is created with that data and the
-    default parameters.
-3.  If a tuple is returned the items in the tuple can provide extra information.
-    Such tuples have to be in the form ``(response, status, headers)``,
-    ``(response, headers)`` or ``(response, status)`` where at least one item
-    has to be in the tuple. The ``status`` value will override the status code
-    and ``headers`` can be a list or dictionary of additional header values.
-4.  If none of that works, Flask will assume the return value is a
+2.  If it's a string, a response object is created with that data and
+    the default parameters.
+3.  If it's a dict, a response object is created using ``jsonify``.
+4.  If a tuple is returned the items in the tuple can provide extra
+    information. Such tuples have to be in the form
+    ``(response, status)``, ``(response, headers)``, or
+    ``(response, status, headers)``. The ``status`` value will override
+    the status code and ``headers`` can be a list or dictionary of
+    additional header values.
+5.  If none of that works, Flask will assume the return value is a
     valid WSGI application and convert that into a response object.
 
 If you want to get hold of the resulting response object inside the view
@@ -717,6 +720,39 @@ return it::
         resp.headers['X-Something'] = 'A value'
         return resp
 
+
+APIs with JSON
+``````````````
+
+A common response format when writing an API is JSON. It's easy to get
+started writing such an API with Flask. If you return a ``dict`` from a
+view, it will be converted to a JSON response.
+
+.. code-block:: python
+
+    @app.route("/me")
+    def me_api():
+        user = get_current_user()
+        return {
+            "username": user.username,
+            "theme": user.theme,
+            "image": url_for("user_image", filename=user.image),
+        }
+
+Depending on your API design, you may want to create JSON responses for
+types other than ``dict``. In that case, use the
+:func:`~flask.json.jsonify` function, which will serialize any supported
+JSON data type. Or look into Flask community extensions that support
+more complex applications.
+
+.. code-block:: python
+
+    @app.route("/users")
+    def users_api():
+        users = get_all_users()
+        return jsonify([user.to_json() for user in users])
+
+
 .. _sessions:
 
 Sessions
diff --git a/flask/app.py b/flask/app.py
index 50775ce4ed..b0b2bc26c8 100644
--- a/flask/app.py
+++ b/flask/app.py
@@ -44,6 +44,7 @@
     url_for,
     get_load_dotenv,
 )
+from .json import jsonify
 from .logging import create_logger
 from .sessions import SecureCookieSessionInterface
 from .signals import (
@@ -2001,6 +2002,9 @@ def make_response(self, rv):
             ``bytes`` (``str`` in Python 2)
                 A response object is created with the bytes as the body.
 
+            ``dict``
+                A dictionary that will be jsonify'd before being returned.
+
             ``tuple``
                 Either ``(body, status, headers)``, ``(body, status)``, or
                 ``(body, headers)``, where ``body`` is any of the other types
@@ -2064,6 +2068,8 @@ def make_response(self, rv):
                 # special logic
                 rv = self.response_class(rv, status=status, headers=headers)
                 status = headers = None
+            elif isinstance(rv, dict):
+                rv = jsonify(rv)
             else:
                 # evaluate a WSGI callable, or coerce a different response
                 # class to the correct type
diff --git a/tests/test_basic.py b/tests/test_basic.py
index b759098f98..7a16ebd491 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -1147,8 +1147,12 @@ def from_response_status():
     def from_wsgi():
         return NotFound()
 
-    assert client.get("/text").data == u"Hällo Wörld".encode("utf-8")
-    assert client.get("/bytes").data == u"Hällo Wörld".encode("utf-8")
+    @app.route('/dict')
+    def from_dict():
+        return {"foo": "bar"}, 201
+
+    assert client.get('/text').data == u'Hällo Wörld'.encode('utf-8')
+    assert client.get('/bytes').data == u'Hällo Wörld'.encode('utf-8')
 
     rv = client.get("/full_tuple")
     assert rv.data == b"Meh"
@@ -1181,6 +1185,10 @@ def from_wsgi():
     assert b"Not Found" in rv.data
     assert rv.status_code == 404
 
+    rv = client.get('/dict')
+    assert rv.json == {"foo": "bar"}
+    assert rv.status_code == 201
+
 
 def test_response_type_errors():
     app = flask.Flask(__name__)

This is something I've been experimenting with in Quart and I don't see a downside. I can't find any old issues relating to this (although I've found it hard to search for). This is roughly an issue, but as a pull request (branch) you can take the code and try it out. Obliviously if you consider this a good idea I'll add much more testing and documentation. 

This supports an increasingly common use-case whereby JSON is the primary response (rather than a templated string). Given Flask simplifies returning HTML responses, it seems fitting that it should also do so for JSON responses. In practice it allows,
```python
@app.route("/")
def index():
    return {
        "api_stuff": "values",
    }
```
which is equivalent to
```python
@app.route("/")
def index():
    return jsonify({
        "api_stuff": "values",
    })
```

### Note 

This doesn't support returning anything other than an associate array at the top level in the JSON response. I'm ok with this as in practice APIs are only extensible if the top level is an associate array.