Skip to content

Latest commit

 

History

History
417 lines (300 loc) · 8.94 KB

README.md

File metadata and controls

417 lines (300 loc) · 8.94 KB

REAL WORLD BACKEND

Real World demo is a "Conduit" like application. Conduit is a social blogging site (i.e. a Medium.com clone). It uses a custom API for all requests, including authentication. You can view a live demo over at https://demo.realworld.io

General functionality:

  • Authenticate users via JWT (login/sign-up pages + logout button on settings page)
  • CRU* users (sign up & settings page - no deleting required)
  • CRUD Articles
  • CR*D Comments on articles (no updating required)
  • GET and display paginated lists of articles
  • Favorite articles
  • Follow other users

Architecture

The code has multiple classes per file, not like Java to take advantage of Kotlin language and keep things short.

routes package holds the controllers, called that way because another kind of controllers (ie: commands for CLI) could exist (in this case a package called commands would be created).

The messages package contains the request/responses sent to controllers. It is left aside as it would be eventually shared with the front-end.

Common handling logic (as error processing) is held in Routes.kt.

The service has three layers:

  • Routes: HTTP layer (controllers) and messages (requests/responses)
    1. Gets data and parameters
    2. Authorize
    3. Authenticate
    4. Validate data and state
    5. Convert to services model
    6. Uses services for validation and execution
    7. Take resulting models and convert them to output messages
  • Services: application logic and data (independent of the others)
    • Called by routes
    • Calls stores (the dependency will be by interfaces)
  • Stores: storage port (with a MongoDB adapter)

Rules of thumb:

  • "All you know binds you" the less each component knows about others, the better.
  • Each layer is in a package
  • A layer package can have subpackages
  • Services can not import anything outside services (except logging)
  • Other packages can only import from services not among them

RealWorld API Spec

  • CORS should be working ok and content type must be json/utf8: application/json;charset=utf8
  • Authenticated endpoints must have the authentication header: Authorization: Token jwt.token.here

JWT

JWT token generation/parsing requires an RSA key pair. To generate a keypair keystore execute:

keytool \
  -genkeypair \
  -keystore keystore.p12 \
  -storetype pkcs12 \
  -storepass storepass \
  -keyalg RSA \
  -validity 999 \
  -alias realWorld \
  -dname "CN=Real World, OU=Development, O=Hexagon, L=Madrid, S=Madrid, C=ES"

Build

From now on assume alias gw='./gradlew'.

  • Build: gw installDist
  • Rebuild: gw clean installDist
  • Run: gw run
  • Watch: gw --no-daemon --continuous runService
  • Test: gw test

API

Users (for authentication)

{
  "user": {
    "email": "jake@jake.jake",
    "token": "jwt.token.here",
    "username": "jake",
    "bio": "I work at statefarm",
    "image": null
  }
}

Profile

{
  "profile": {
    "username": "jake",
    "bio": "I work at statefarm",
    "image": "https://static.productionready.io/images/smiley-cyrus.jpg",
    "following": false
  }
}

Single Article

{
  "article": {
    "slug": "how-to-train-your-dragon",
    "title": "How to train your dragon",
    "description": "Ever wonder how?",
    "body": "It takes a Jacobian",
    "tagList": ["dragons", "training"],
    "createdAt": "2016-02-18T03:22:56.637Z",
    "updatedAt": "2016-02-18T03:48:35.824Z",
    "favorited": false,
    "favoritesCount": 0,
    "author": {
      "username": "jake",
      "bio": "I work at statefarm",
      "image": "https://i.stack.imgur.com/xHWG8.jpg",
      "following": false
    }
  }
}

Multiple Articles

{
  "articles":[{
    "slug": "how-to-train-your-dragon",
    "title": "How to train your dragon",
    "description": "Ever wonder how?",
    "body": "It takes a Jacobian",
    "tagList": ["dragons", "training"],
    "createdAt": "2016-02-18T03:22:56.637Z",
    "updatedAt": "2016-02-18T03:48:35.824Z",
    "favorited": false,
    "favoritesCount": 0,
    "author": {
      "username": "jake",
      "bio": "I work at statefarm",
      "image": "https://i.stack.imgur.com/xHWG8.jpg",
      "following": false
    }
  }, {
    "slug": "how-to-train-your-dragon-2",
    "title": "How to train your dragon 2",
    "description": "So toothless",
    "body": "It a dragon",
    "tagList": ["dragons", "training"],
    "createdAt": "2016-02-18T03:22:56.637Z",
    "updatedAt": "2016-02-18T03:48:35.824Z",
    "favorited": false,
    "favoritesCount": 0,
    "author": {
      "username": "jake",
      "bio": "I work at statefarm",
      "image": "https://i.stack.imgur.com/xHWG8.jpg",
      "following": false
    }
  }],
  "articlesCount": 2
}

Single Comment

{
  "comment": {
    "id": 1,
    "createdAt": "2016-02-18T03:22:56.637Z",
    "updatedAt": "2016-02-18T03:22:56.637Z",
    "body": "It takes a Jacobian",
    "author": {
      "username": "jake",
      "bio": "I work at statefarm",
      "image": "https://i.stack.imgur.com/xHWG8.jpg",
      "following": false
    }
  }
}

Multiple Comments

{
  "comments": [{
    "id": 1,
    "createdAt": "2016-02-18T03:22:56.637Z",
    "updatedAt": "2016-02-18T03:22:56.637Z",
    "body": "It takes a Jacobian",
    "author": {
      "username": "jake",
      "bio": "I work at statefarm",
      "image": "https://i.stack.imgur.com/xHWG8.jpg",
      "following": false
    }
  }]
}

List of Tags

{
  "tags": [
    "reactjs",
    "angularjs"
  ]
}

Errors and Status Codes

If a request fails any validations, expect a 422 and errors in the following format:

{
  "errors":{
    "body": [
      "can't be empty"
    ]
  }
}

Other status codes:

401 for Unauthorized requests, when a request requires authentication, but it isn't provided

403 for Forbidden requests, when a request may be valid but the user doesn't have permissions to perform the action

404 for Not found requests, when a resource can't be found to fulfill the request

Endpoints:

Get Profile

GET /api/profiles/:username

Authentication optional, returns a Profile

Follow user

POST /api/profiles/:username/follow

Authentication required, returns a Profile

No additional parameters required

Unfollow user

DELETE /api/profiles/:username/follow

Authentication required, returns a Profile

No additional parameters required

Create Article

POST /api/articles

Example request body:

{
  "article": {
    "title": "How to train your dragon",
    "description": "Ever wonder how?",
    "body": "You have to believe",
    "tagList": ["reactjs", "angularjs", "dragons"]
  }
}

Authentication required, will return an Article

Required fields: title, description, body

Optional fields: tagList as an array of Strings

Update Article

PUT /api/articles/:slug

Example request body:

{
  "article": {
    "title": "Did you train your dragon?"
  }
}

Authentication required, returns the updated Article

Optional fields: title, description, body

The slug also gets updated when the title is changed

Delete Article

DELETE /api/articles/:slug

Authentication required

Favorite Article

POST /api/articles/:slug/favorite

Authentication required, returns the Article

No additional parameters required

Unfavorite Article

DELETE /api/articles/:slug/favorite

Authentication required, returns the Article

No additional parameters required

List Articles

GET /api/articles

Returns most recent articles globally by default, provide tag, author or favorited query parameter to filter results

Query Parameters:

Filter by tag:

?tag=AngularJS

Filter by author:

?author=jake

Favorited by user:

?favorited=jake

Limit number of articles (default is 20):

?limit=20

Offset/skip number of articles (default is 0):

?offset=0

Authentication optional, will return multiple articles, ordered by most recent first

Feed Articles

GET /api/articles/feed

Can also take limit and offset query parameters like List Articles

Authentication required, will return multiple articles created by followed users, ordered by most recent first.

Get Article

GET /api/articles/:slug

No authentication required, will return single article

Add Comments to an Article

POST /api/articles/:slug/comments

Example request body:

{
  "comment": {
    "body": "His name was my name too."
  }
}

Authentication required, returns the created Comment

Required field: body

Get Comments from an Article

GET /api/articles/:slug/comments

Authentication optional, returns multiple comments

Delete Comment

DELETE /api/articles/:slug/comments/:id

Authentication required

Get Tags

GET /api/tags

No authentication required, returns a List of Tags