• This repository has been archived on 28/Sep/2022
  • Stars
    star
    428
  • Rank 97,868 (Top 2 %)
  • Language
    PHP
  • Created over 6 years ago
  • Updated almost 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Exemplary real world application built with Slim

RealWorld Example App

Slim codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the RealWorld spec and API.

Demo    RealWorld

This codebase was created to demonstrate a fully fledged fullstack application built with Slim including CRUD operations, authentication, routing, pagination, and more.

We've gone to great lengths to adhere to the Slim community styleguides & best practices.

For more information on how to this works with other frontends/backends, head over to the RealWorld repo.

How it works

The basic idea behind this app is to provide a backend web service for the website Conduit made by the Thinkster team.

It is designed as an api which process requests and return JSON responses.

tl;dr commands

git clone https://github.com/gothinkster/slim-php-realworld-example-app.git
cd slim-php-realworld-example-app
composer install

Edit .env file with your database configurations.

composer refresh-database 
composer start

visit http://localhost:8080/api/articles from your browser

Or follow this detailed guide to install and understand the code

Getting Started

Pre-requisites

Make sure you have php, mysql and composer installed on your machine.

Installation

You should start by cloning the repository into your local machine.

git clone https://github.com/gothinkster/slim-php-realworld-example-app.git
cd slim-php-realworld-example-app

Dependencies

The app is built using Slim. However, there ara extra packages used by the app must be installed.

Install Dependencies

composer install

List of Dependencies

Environments Variables

All the app environments variables are stored in the .env file.

The command composer install will copy .env.example to .env which should have your own variables and never shared or committed into git.

Check the .env file and edit the variables according to your environments. (Database name/user/password). The .env is loaded using vlucas/phpdotenv through these lines of code

Code Overview

Directory Structure

Open the project directory using your favorite editor.

The app follows the structure of Slim skeleton application with minor changes. The skeleton is a good starting point when developing with Slim framework. A detailed overview of the directory structure can be found at this page.

Design Architecture

Api Design

The api is designed according to the RealWorld specifications. Make sure to familiarized yourself with all endpoints specified in the Full API Spec

Code Design

The code utilizes the MVC pattern where requests are redirected to a controller to process the request and returned a JSON response. Persistence of data is managed by the models which provide the source of truth and the database status.

Data Structure

Database Schema

The app is built using a relational database (e.g. MySQL), and consists of 8 tables.

Setup the database Create a database in MySQL and name it conduit or whatever you prefer. Next, don't forget to update Environments Variables in .env file. Check Database Documentation for details on the schema.

Database Migration:

Database migrations or Schema migration is where the app defines the database structure and blueprints. It also holds the history of any changes made to the database schema and provides easy way to rollback changes to older version.

The app database migrations can be found at the migration directory. Migrations are performed using Phinx.

Migrate the Database

To create all the tables using migration run the following command from the project directory.

php vendor/bin/phinx migrate

Data Models

The data is managed by models which represent the business entities of the app. There are four models User, Article, Comment, and Tag. They can be found at Models Directory. Each model has corresponding table in the database. These models extends Illuminate\Database\Eloquent\Model which provides the ORM implementations.

Relationships with other models are defined by each model using Eloquent. For example, User-Comment is a one-to-many relationship which is defined by the User model and by the Comment model. This relationship is stored in the database by having a foreign key user_id in the comments table.

Beside The four tables in the database representing each model, the database has three other tables to store many-to-many relationships (article_tag, user_favorite, users_following). For example, An article can have many tags, and a tag can be assigned to many articles. This relationship is defined by the Article model and the Tag model, and is stored in the table article_tag.

Data Seeding To populate the database with data for testing and experimenting with the code. Run:

php vendor/bin/phinx migrate
php vendor/bin/phinx seed:run

To edit how the data is seeded check the file: DataSeeder.

The command composer refresh-database will rollback all migrations, migrate the database and seed the data. (Note: all data will be lost from the database)

The Slim Application

Start the app by running the following command:

composer start

This command will spin a local php server which is enough for testing. You can check the api by visiting http://localhost:8080/api/articles

To check all endpoints you need an HTTP client e.g Postman. There is Postman collection made by Thinkster team you could use.

Entry Point:

The server will direct all requests to index.php. There, we boot the app by creating an instance of Slim\App and require all the settings and relevant files.

Finally, we run the app by calling $app->run(), which will process the request and send the response.

We include four important files into the index.php: settings.php, dependencies.php, middleware.php, routes.php I's a good idea to check them before continuing.

The App Instance

The instance of Slim\App ($app) holds the app settings, routes, and dependencies.

We register routes and methods by calling methods on the $app instance.

More importantly, the $app instance has the Container which register the app dependencies to be passed later to the controllers.

Container Dependencies and Services

In different parts of the application we need to use other classes and services. These classes and services also depends on other classes. Managing these dependencies becomes easier when we have a container to hold them. Basically, we configure these classes and store them in the container. Later, when we need a service or a class we ask the container, and it will instantiate the class based on our configuration and return it.

The container is configured in the dependencies.php. We start be retrieving the container from the $app instance and configure the required services:

    $container = $app->getContainer();
    
    $container['logger'] = function ($c) {
        $settings = $c->get('settings')['logger'];
        $logger = new Monolog\Logger($settings['name']);
        $logger->pushProcessor(new Monolog\Processor\UidProcessor());
        $logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], $settings['level']));
    
        return $logger;
    };

The above code registers a configured instance of the logger in the container. Later we can ask for the logger

    $logger = $container->get('logger');
    $logger->info('log message');

We register two middleware with the container:

We will see them in action later in the Authentication section.

    // Jwt Middleware
    $container['jwt'] = function ($c) {
    
        $jws_settings = $c->get('settings')['jwt'];
    
        return new \Slim\Middleware\JwtAuthentication($jws_settings);
    };
    
    // Optional Auth Middleware
    $container['optionalAuth'] = function ($c) {
      return new OptionalAuth($c);
    };

Service Providers

The above services example will not be instantiated until we call for them. However, we could use a service provider to have some services available without retrieving them from the container.

Our app use Eloquent ORM to handle our data models. Eloquent must be configured and booted. We do this in the EloquentServiceProvider class, and register the service provider with the container.

    $container->register(new \Conduit\Services\Database\EloquentServiceProvider());

For more details check Dependency Container documentations.

Request-Response Cycle

All requests go through the same cycle: routing > middleware > conroller > response

Routes:

Check the list of endpoints defined by the RealWorld API Spec

All the app routes are defined in the routes.php file.

The Slim $app variable is responsible for registering the routes. You will notice that all routes are enclosed in the group method which gives the prefix api to all routes: http::/localhost/api

Every route is defined by a method corresponds to the HTTP verb. For example, a post request to register a user is defined by:

    $this->post('/users', RegisterController::class . ':register')->setName('auth.register');

Notice: we use $this because where are inside a closure that is bound to $app;

The method, post(), defines /api/users endpoint and direct the request to method register on RegisterController class.

Middleware

In a Slim app, you can add middleware to all incoming routes, to a specific route, or to a group of routes. Check the documentations

In this app we add some middleware to specific routes. For example, to access /api/articles POST endpoint, the request will go through $jwtMiddleware

    $this->post('/articles', ArticleController::class . ':store')->add($jwtMiddleware)->setName('article.store');

see Authentication for details

Also, We add some global middleware to apply to all requests in middleware.php. CORS Middleware for example.

see CORS for details

Controllers

After passing through all assigned middleware, the request will be processed by a controller.

Note: You could process the request inside a closure passed as the second argument to the method defining the route. For example, the last route, which is left as an example from the skeleton project, handles the request in a closure Check the documentations.

The controller's job is to validate the request data, check for authorization, process the request by calling a model or do other jobs, and eventually return a response in the form of JSON response.

// TODO : Explain how dependencies are injected to the controller.

Authentication and Security

Authentication (JWT)

The api routes can be open to the public without authentication e.g Get Article. Some routes must be authenticated before being processed e.g Follow user.
Other routes require optional authentication and can be submitted without authentication. However, when the request has a Token, the request must be authenticated. This will make a difference in that the request's user identity will be know when we have an authenticated user, and the response must reflect that. For example, the Get Profile endpoint has an optional authentication. The response will be a profile of a user, and the value of following in the response will depend on whether we have aToken in the request.

Basic Idea

Unlike traditional web application, when designing a RESTful Api, when don't have a session to authenticate. On popular way to authenticate api requests is by using JWT.

The basic workflow of JWT is that our application will generate a token and send it with the response when the user sign up or login. The user will keep this token and send it back with any subsequent requests to authenticate his request. The generated token will have header, payload, and a signature. It also should have an expiration time and other data to identify the subject/user of the token. For more details, the JWT Introduction is a good resource.

Dealing with JWT is twofold:

  • Generate a JWT and send to the user when he sign up or login using his email/password.
  • Verify the validity of JWT submitted with any subsequent requests.

Generating The Token

We generate the Token when the user sign up or login using his email/password. This is done in the RegisterController and LoginController by the Auth service class.

Review Container Dependencies about the auth service.

Finally, we send the token with the response back to the user/client.

JWT Verification

To verify the JWT Token we are using tuupola/slim-jwt-auth library. The library provides a middleware to add to the protected routes. The documentations suggest adding the middleware to app globally and define the protected routes. However, in this app, we are taking slightly different approach.

We add a configured instance of the middleware to the Container, and then add the middleware to every protected route individually.

Review Container Dependencies about registering the middleware.

In the routes.php file, we resolve the middleware out of the container and assign to the variable $jwtMiddleware

Then, when defining the protected route, we add the middleware using the add method:

        $jwtMiddleware = $this->getContainer()->get('jwt');
        $this->post('/articles', ArticleController::class . ':store')->add($jwtMiddleware)

The rest is on the tuupola/slim-jwt-auth to verify the token. If the token is invalid or not provided, a 401 response will be returned. Otherwise, the request will be passed to the controller for processing.

Optional Routes

For the optional authentication, we create a custom middleware OptionalAuth. The middleware will check if there a token present in the request header, it will invoke the jwt middleware to verify the token.

Again, we use the OptionalAuth middleware by store it in Container and retrieve it to add to the optional routes.

        $optionalAuth = $this->getContainer()->get('optionalAuth');
        $this->get('/articles/feed', ArticleController::class . ':index')->add($optionalAuth);

Authorization

Some routes required authorization to verify that user is authorized to submit the request. For example, when a user wants to edit an article, we need to verify that he is the owner of the article.

The authorization is handled by the controller. Simply, the controller will compare the article's user_id with request's user id. If not authorized, the controller will return a 403 response.

        if ($requestUser->id != $article->user_id) {
            return $response->withJson(['message' => 'Forbidden'], 403);
        }

However, in a bigger application you might want to implement more robust authorization system.

Security

CORS

CORS is used when the request is coming from a different host. By default, web browsers will prevent such requests. The browser will start by sending an OPTION request to the server to get the approval and then send the actual request.

Therefor, we handle cross-origin HTTP requests by making two changes to our app:

  • Allow OPTIONS requests.
  • Return the approval in the response.

This is done in the by adding two middleware in the middleware.php file The first middleware will add the required headers for CORS approval. And the second, deals with issue of redirect when the route ends with a slash.

For more information check Slim documentations:

Test

composer test

More Repositories

1

realworld

"The mother of all demo apps" — Exemplary fullstack Medium.com clone powered by React, Angular, Node, Django, and many more
TypeScript
78,236
star
2

react-redux-realworld-example-app

Exemplary real world application built with React + Redux
JavaScript
5,538
star
3

angular-realworld-example-app

Exemplary real world application built with Angular
TypeScript
5,079
star
4

vue-realworld-example-app

An exemplary real-world application built with Vue.js, Vuex, axios and different other technologies. This is a good example to discover Vue for beginners.
JavaScript
4,053
star
5

node-express-realworld-example-app

TypeScript
3,524
star
6

golang-gin-realworld-example-app

Exemplary real world application built with Golang + Gin
Go
2,440
star
7

aspnetcore-realworld-example-app

ASP.NET Core backend implementation for RealWorld
C#
1,872
star
8

django-realworld-example-app

Python
1,593
star
9

spring-boot-realworld-example-app

Example Spring codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the RealWorld API spec.
Java
1,252
star
10

react-mobx-realworld-example-app

Exemplary real world application built with React + MobX
JavaScript
1,245
star
11

laravel-realworld-example-app

Exemplary real world backend API built with Laravel
PHP
1,186
star
12

flask-realworld-example-app

Exemplary real world JSON API built with Flask (Python)
Python
891
star
13

elixir-phoenix-realworld-example-app

Exemplary real world application built with Elixir + Phoenix
Elixir
887
star
14

angularjs-realworld-example-app

Exemplary real world application built with Angular 1.5 + ES6
JavaScript
450
star
15

kotlin-spring-realworld-example-app

Kotlin
443
star
16

rails-realworld-example-app

Ruby
427
star
17

realworld-starter-kit

Starter kit for new RealWorld framework implementations
407
star
18

swift

A better way to learn swift.
274
star
19

koa-knex-realworld-example

JavaScript
241
star
20

aurelia-realworld-example-app

Exemplary real world application built with Aurelia
JavaScript
184
star
21

ember-realworld

Ember.js RealWorld Implementation
JavaScript
182
star
22

web-components-realworld-example-app

Exemplary real world application built with Vanilla JS Web Components
JavaScript
171
star
23

scala-play-realworld-example-app

Exemplary real world application built with Scala 2.13 & Play
Scala
154
star
24

node-express-prisma-v1-official-app

Official real world application built with Node + Express + Typescript + Prisma
TypeScript
147
star
25

gcp-datastore-cloud-functions-realworld-example-app

Serverless GCP Cloud Functions + Datastore implementation of RealWorld Backend
JavaScript
144
star
26

cakephp-realworld-example-app

PHP
105
star
27

moleculer-node-realworld-example-app

Exemplary real world application built with Moleculer
JavaScript
103
star
28

clojurescript-reframe-realworld-example-app

Exemplary real world application built with ClojureScript + re-frame
Clojure
102
star
29

apprun-realworld-example-app

Exemplary real world application built with AppRun
TypeScript
101
star
30

fsharp-realworld-example-app

Exemplary real world application built with F#
F#
95
star
31

hapijs-realworld-example-app

Real World Hapi.JS + Mongoose backend implementations
JavaScript
78
star
32

clojurescript-keechma-realworld-example-app

Exemplary real world application built with ClojureScript & Keechma
Clojure
72
star
33

reasonml-realworld-example-app

Exemplary real world application built with ReasonML
Reason
59
star
34

conduit-bootstrap-template

HTML
33
star
35

dojo-realworld-example-app

Exemplary real world application built with Dojo2
TypeScript
29
star
36

angularfire-slack

JavaScript
27
star
37

QEWD-realworld-example-app

JavaScript
22
star
38

crizmas-mvc-realworld-example-app

Exemplary real world application built with Crizmas MVC (React)
JavaScript
17
star
39

express-prisma-official-app

TypeScript
14
star
40

jswiki

The wiki for modern Javascript terminology
HTML
13
star
41

conduit-angularjs-final

JavaScript
10
star
42

conduit-sass

HTML
8
star
43

conduit-django

Python
8
star
44

thinkdown

Preview markdown as it appears on https://thinkster.io
CSS
6
star
45

test-routes

JavaScript
6
star
46

conduit-react-client-final

JavaScript
4
star
47

conduit-rails4-api-final

Ruby
4
star
48

course-boilerplate

3
star
49

mean-stack-tutorial

3
star
50

realworld-api-action

GitHub Action running a Postman collection to test the compliance of a backend implementation of RealWorld specs
JavaScript
3
star
51

api-realworld

TypeScript
2
star
52

gothinkster.github.io

HTML
2
star
53

conduit-rails4

Ruby
2
star
54

conduit-rails

Ruby
2
star
55

conduit-angular

HTML
1
star
56

thinkster-author

Authoring site for Thinkster.io Authors
1
star
57

blog

Thinkster blog
1
star
58

imperial

JavaScript
1
star
59

conduit-api

TypeScript
1
star
60

transactional

1
star
61

realworld-e2e-tests

E2e test suite for RealWorld demo
JavaScript
1
star
62

realworld-tribe-angular-app

HTML
1
star
63

realworld-tribe-nestjs-app

TypeScript
1
star