diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php b/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
index 2b81b14..310d843 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
@@ -43,6 +43,7 @@ public function getDerivativeDefinitions(array $base_plugin_definition) {
         $this->derivatives[$entity_type] = array(
           'id' => 'entity:' . $entity_type,
           'entity_type' => $entity_type,
+          'serialization_class' => $entity_info['class'],
           'label' => $entity_info['label'],
         );
         $this->derivatives[$entity_type] += $base_plugin_definition;
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php b/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
index 829013f..fa53bbb 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
@@ -53,24 +53,36 @@ public function permissions() {
    */
   public function routes() {
     $collection = new RouteCollection();
-
+    $name = strtr($this->plugin_id, ':', '.');
+    $prefix = strtr($this->plugin_id, ':', '/');
     $methods = $this->requestMethods();
     foreach ($methods as $method) {
       $lower_method = strtolower($method);
       // Only expose routes where the HTTP request method exists on the plugin.
       if (method_exists($this, $lower_method)) {
-        $prefix = strtr($this->plugin_id, ':', '/');
-        $route = new Route("/$prefix/{id}", array(
-          '_controller' => 'Drupal\rest\RequestHandler::handle',
-          // Pass the resource plugin ID along as default property.
-          '_plugin' => $this->plugin_id,
-        ), array(
-          // The HTTP method is a requirement for this route.
-          '_method' => $method,
-          '_permission' => "restful $lower_method $this->plugin_id",
-        ));
-
-        $name = strtr($this->plugin_id, ':', '.');
+        // Special case for resource creation via POST: Add a route that does
+        // not require an ID.
+        if ($method == 'POST') {
+          $route = new Route("/$prefix", array(
+            '_controller' => 'Drupal\rest\RequestHandler::handle',
+            '_plugin' => $this->plugin_id,
+            'id' => NULL,
+          ), array(
+            // The HTTP method is a requirement for this route.
+            '_method' => $method,
+            '_permission' => "restful $lower_method $this->plugin_id",
+          ));
+        }
+        else {
+          $route = new Route("/$prefix/{id}", array(
+            '_controller' => 'Drupal\rest\RequestHandler::handle',
+            '_plugin' => $this->plugin_id,
+          ), array(
+            // The HTTP method is a requirement for this route.
+            '_method' => $method,
+            '_permission' => "restful $lower_method $this->plugin_id",
+          ));
+        }
         $collection->add("$name.$method", $route);
       }
     }
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/DBLogResource.php b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/DBLogResource.php
index 5a1fe31..13af57b 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/DBLogResource.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/DBLogResource.php
@@ -47,14 +47,14 @@ public function routes() {
    */
   public function get($id = NULL) {
     if ($id) {
-      $result = db_query("SELECT * FROM {watchdog} WHERE wid = :wid", array(':wid' => $id))
+      $record = db_query("SELECT * FROM {watchdog} WHERE wid = :wid", array(':wid' => $id))
         ->fetchObject();
-      if (!empty($result)) {
+      if (!empty($record)) {
         // Serialization is done here, so we indicate with NULL that there is no
         // subsequent serialization necessary.
         $response = new ResourceResponse(NULL, 200, array('Content-Type' => 'application/json'));
         // @todo remove hard coded format here.
-        $response->setContent(drupal_json_encode($result));
+        $response->setContent(drupal_json_encode($record));
         return $response;
       }
     }
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
index 689fd84..df8cc5a 100644
--- a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Annotation\Plugin;
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\rest\Plugin\ResourceBase;
 use Drupal\rest\ResourceResponse;
@@ -21,6 +22,7 @@
  * @Plugin(
  *   id = "entity",
  *   label = @Translation("Entity"),
+ *   serialization_class = "Drupal\Core\Entity\Entity",
  *   derivative = "Drupal\rest\Plugin\Derivative\EntityDerivative"
  * )
  */
@@ -47,6 +49,37 @@ public function get($id) {
   }
 
   /**
+   * Responds to entity POST requests.
+   *
+   * @param mixed $id
+   *   Ignored.
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   *
+   * @return \Drupal\rest\ResourceResponse
+   *   The HTTP response object.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
+   */
+  public function post($id, EntityInterface $entity) {
+    // Verify that the deserialized entity is of the type that we expect to
+    // prevent security issues.
+    $definition = $this->getDefinition();
+    if ($entity->entityType() != $definition['entity_type']) {
+      throw new HttpException(400, 'Bad Request');
+    }
+    try {
+      $entity->save();
+      $url = url(strtr($this->plugin_id, ':', '/') . '/' . $entity->id(), array('absolute' => TRUE));
+      // 201 Created responses have an empty body.
+      return new ResourceResponse(NULL, 201, array('Location' => $url));
+    }
+    catch (EntityStorageException $e) {
+      throw new HttpException(500, 'Internal Server Error', $e);
+    }
+  }
+
+  /**
    * Responds to entity DELETE requests.
    *
    * @param mixed $id
diff --git a/core/modules/rest/lib/Drupal/rest/RequestHandler.php b/core/modules/rest/lib/Drupal/rest/RequestHandler.php
index 53011cf..d927243 100644
--- a/core/modules/rest/lib/Drupal/rest/RequestHandler.php
+++ b/core/modules/rest/lib/Drupal/rest/RequestHandler.php
@@ -34,19 +34,28 @@ public function handle(Request $request, $id = NULL) {
     $resource = $this->container
       ->get('plugin.manager.rest')
       ->getInstance(array('id' => $plugin));
+
+    // Deserialze incoming data if available.
+    $serializer = $this->container->get('serializer');
     $received = $request->getContent();
-    // @todo De-serialization should happen here if the request is supposed
-    // to carry incoming data.
+    $unserialized = NULL;
+    if (!empty($received)) {
+      $definition = $resource->getDefinition();
+      $class = $definition['serialization_class'];
+      $unserialized = $serializer->deserialize($received, $class, 'drupal_jsonld');
+    }
+
+    // Invoke the operation on the resource plugin.
     try {
-      $response = $resource->{$method}($id, $received);
+      $response = $resource->{$method}($id, $unserialized, $request);
     }
     catch (HttpException $e) {
       return new Response($e->getMessage(), $e->getStatusCode(), $e->getHeaders());
     }
+
+    // Serialize the outgoing data for the response, if available.
     $data = $response->getResponseData();
     if ($data != NULL) {
-      // Serialize the response data.
-      $serializer = $this->container->get('serializer');
       // @todo Replace the format here with something we get from the HTTP
       //   Accept headers. See http://drupal.org/node/1833440
       $output = $serializer->serialize($data, 'drupal_jsonld');
diff --git a/core/modules/rest/lib/Drupal/rest/ResourceResponse.php b/core/modules/rest/lib/Drupal/rest/ResourceResponse.php
index a93f25e..d79653a 100644
--- a/core/modules/rest/lib/Drupal/rest/ResourceResponse.php
+++ b/core/modules/rest/lib/Drupal/rest/ResourceResponse.php
@@ -11,6 +11,11 @@
 
 /**
  * Contains data for serialization before sending the response.
+ *
+ * We do not want to abuse the $content property on the Response class to store
+ * our response data. $content implies that the provided data must either be a
+ * string or an object with a __toString() method, which is not a requirement
+ * for data used here.
  */
 class ResourceResponse extends Response {
 
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php b/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php
new file mode 100644
index 0000000..f5ef159
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\test\CreateTest.
+ */
+
+namespace Drupal\rest\Tests;
+
+use Drupal\rest\Tests\RESTTestBase;
+
+/**
+ * Tests resource creation on user, node and test entities.
+ */
+class CreateTest extends RESTTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('rest', 'entity_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Create resource',
+      'description' => 'Tests the creation of resources.',
+      'group' => 'REST',
+    );
+  }
+
+  /**
+   * Tests several valid and invalid create requests on all entity types.
+   */
+  public function testCreate() {
+    $serializer = drupal_container()->get('serializer');
+    // @todo once EntityNG is implemented for other entity types test all other
+    // entity types here as well.
+    $entity_type = 'entity_test';
+
+    $this->enableService('entity:' . $entity_type);
+    // Create a user account that has the required permissions to create
+    // resources via the web API.
+    $account = $this->drupalCreateUser(array('restful post entity:' . $entity_type));
+    $this->drupalLogin($account);
+
+    $entity_values = $this->entityValues($entity_type);
+    $entity = entity_create($entity_type, $entity_values);
+    $serialized = $serializer->serialize($entity, 'drupal_jsonld');
+    // Create the entity over the web API.
+    $response = $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, 'application/vnd.drupal.ld+json');
+    $this->assertResponse('201', 'HTTP response code is correct.');
+
+    // Get the new entity ID from the location header and try to read it from
+    // the database.
+    $location_url = $this->responseHeaders['location'];
+    $url_parts = explode('/', $location_url);
+    $id = end($url_parts);
+    $loaded_entity = entity_load($entity_type, $id);
+    $this->assertNotIdentical(FALSE, $loaded_entity, 'The new ' . $entity_type . ' was found in the database.');
+    $this->assertEqual($entity->uuid(), $loaded_entity->uuid(), 'UUID of created entity is correct.');
+    // @todo Remove the user reference field for now until deserialization for
+    // entity references is implemented.
+    unset($entity_values['user_id']);
+    foreach ($entity_values as $property => $value) {
+      $actual_value = $loaded_entity->get($property);
+      $this->assertEqual($value, $actual_value->value, 'Created property ' . $property . ' expected: ' . $value . ', actual: ' . $actual_value->value);
+    }
+
+    // Try to create an entity without proper permissions.
+    $this->drupalLogout();
+    $response = $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, 'application/vnd.drupal.ld+json');
+    $this->assertResponse(403);
+
+    // Try to create a resource which is not web API enabled.
+    $this->enableService(FALSE);
+    $this->drupalLogin($account);
+    $this->httpRequest('entity/entity_test', 'POST', $serialized, 'application/vnd.drupal.ld+json');
+    $this->assertResponse(404);
+
+    // @todo Once EntityNG is implemented for other entity types add a security
+    // test. It should not be possible for example to create a test entity on a
+    // node resource route.
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php b/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php
index 5002397..9ea37c1 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php
@@ -52,7 +52,7 @@ public function testWatchdog() {
 
     $response = $this->httpRequest("dblog/$id", 'GET', NULL, 'application/json');
     $this->assertResponse(200);
-    $this->assertHeader('Content-Type', 'application/json');
+    $this->assertHeader('content-type', 'application/json');
     $log = drupal_json_decode($response);
     $this->assertEqual($log['wid'], $id, 'Log ID is correct.');
     $this->assertEqual($log['type'], 'rest_test', 'Type of log message is correct.');
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
index acf4802..3640e48 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
@@ -85,7 +85,9 @@ protected function httpRequest($url, $method, $body = NULL, $format = 'applicati
     $header_lines = explode("\r\n", $header);
     foreach ($header_lines as $line) {
       $parts = explode(':', $line, 2);
-      $this->responseHeaders[$parts[0]] = isset($parts[1]) ? trim($parts[1]) : '';
+      // Store the header keys lower cased to be more robust. Headers are case
+      // insensitive according to RFC 2616.
+      $this->responseHeaders[strtolower($parts[0])] = isset($parts[1]) ? trim($parts[1]) : '';
     }
 
     $this->verbose($method . ' request to: ' . $url .
@@ -99,25 +101,38 @@ protected function httpRequest($url, $method, $body = NULL, $format = 'applicati
   /**
    * Creates entity objects based on their types.
    *
-   * Required properties differ from entity type to entity type, so we keep a
-   * minimum mapping here.
-   *
    * @param string $entity_type
-   *   The type of the entity that should be created..
+   *   The type of the entity that should be created.
    *
    * @return \Drupal\Core\Entity\EntityInterface
    *   The new entity object.
    */
   protected function entityCreate($entity_type) {
+    return entity_create($entity_type, $this->entityValues($entity_type));
+  }
+
+  /**
+   * Provides an array of suitable property values for an entity type.
+   *
+   * Required properties differ from entity type to entity type, so we keep a
+   * minimum mapping here.
+   *
+   * @param string $entity_type
+   *   The type of the entity that should be created.
+   *
+   * @return array
+   *   An array of values keyed by property name.
+   */
+  protected function entityValues($entity_type) {
     switch ($entity_type) {
       case 'entity_test':
-        return entity_create('entity_test', array('name' => $this->randomName(), 'user_id' => 1));
+        return array('name' => $this->randomName(), 'user_id' => 1);
       case 'node':
-        return entity_create('node', array('title' => $this->randomString()));
+        return array('title' => $this->randomString());
       case 'user':
-        return entity_create('user', array('name' => $this->randomName()));
+        return array('name' => $this->randomName());
       default:
-        return entity_create($entity_type, array());
+        return array();
     }
   }
 
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php b/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
index 0c710e5..7ebe5f2 100644
--- a/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
+++ b/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php
@@ -53,7 +53,7 @@ public function testRead() {
       // Read it over the web API.
       $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, 'application/vnd.drupal.ld+json');
       $this->assertResponse('200', 'HTTP response code is correct.');
-      $this->assertHeader('Content-Type', 'application/vnd.drupal.ld+json');
+      $this->assertHeader('content-type', 'application/vnd.drupal.ld+json');
       $data = drupal_json_decode($response);
       // Only assert one example property here, other properties should be
       // checked in serialization tests.
