Table of Contents
A template to jumpstart your new greenfield project. If you are a startup or an entrepreneur thinking to start a new project with php and symfony as a BE and Typescript and React as a FE, consider to use Symfony React skeleton and save weeks of development.
This project covers common (repetitive) parts of most greenfield projects. Fully functional and communicating BE and FE parts via REST, CI pipeline with robust tests and helm deployment.
On top of it you will receive BE, FE and DevOps best practices already implemented. You software development team can follow these guidelines to deliver sustainable and maintainable top quality product.
Don't forget to give the project a star!
Live Demo - FE - production environmentΒ |Β Live Demo - BE status - production environment
Live Demo - FE - dev environmentΒ |Β Live Demo - BE status - dev environment
- git
- docker (and docker compose) installed. You don't need Docker Desktop for this project.
- node
- pnpm
- OPTIONAL: helm for deploying to kubernetes cluster
- Clone the repo. Replace {myproject} with a name of your new project/app. It is important to stay in one terminal window during all install steps, otherwise PROJECT_NAME needs to be set again.
PROJECT_NAME={myproject}
git clone https://github.com/petrzivny/symfony-react-skeleton.git $PROJECT_NAME && cd $PROJECT_NAME
- Setup environmental variables by using prepared template
cp api/.env.local.dist api/.env.local
- Change name of your project (application) from symfony-react-skeleton to {myproject}. You can do it in your editor or use following command. Choose only one.
For Linux:or for MacOssed -i "s/\${PROJECT_NAME:-symfony-react-skeleton}/$PROJECT_NAME/g" .docker/docker-compose.yaml .docker/docker-compose-prod.yaml
or manuallysed -i '' "s/\${PROJECT_NAME:-symfony-react-skeleton}/$PROJECT_NAME/g" .docker/docker-compose.yaml .docker/docker-compose-prod.yaml
# Replace all occurrences of string "${PROJECT_NAME:-symfony-react-skeleton}" with {myproject}.
- Build BE docker images and run them as docker containers in dev mode
Don't worry about nginx and php Errors or Warnings, it just means docker images needs to be build for a first time. Try
cd .docker && docker compose --env-file ../api/.env.local up -d
docker ps
. 3 containers should be up and running (php, nginx, postgres), if not, trydocker ps -a
anddocker log
. - Install php dependencies (inside php docker container)
docker exec -it ${PROJECT_NAME}_php composer i
- Visit http://localhost:81/api/status to check if BE is running properly (you should see 200 JSON response with debug info). I recommend to use this chrome extension to format json responses.
- Install javascript dependencies
cd ../fe && pnpm install
- Run FE hot reload dev server
pnpm run dev
- There should be newly opened http://localhost:5173/ window in your browser with react as a FE framework communicating with BE Symfony. The green value is fetched from BE database. You are ready to start local development. Happy coding. Maybe time to give this project a star β, thank you. π
Next time you only need to perform points 4. and 8. to start developing. I recommend to set up an alias for them.
At this moment your only remote repository is origin: https://github.com/petrzivny/symfony-react-skeleton.git. But you need to have your own remote repository connected too (I recommend to leave original repository if you want to contribute this public project in the future).
# rename origin: https://github.com/petrzivny/symfony-react-skeleton.git to template: https://github.com/petrzivny/symfony-react-skeleton.git
git remote rename origin template
# Create a new repository (preferably on GitHub to use all features of this template)
git remote add origin {url_of_your_repo}
#eg: git remote add origin [email protected]/petrzivny/myproject.git
git push -u origin main
Take a look into your GitHub repository. All code should be there and your first GitHub Actions pipeline should be initiated. At this point you will need to configure self-hosted runner(s).
- Docker to run complete dev environment (php + nginx + PostgreSQL)
- Symfony framework as a backed REST api
- Independent on any used frontend. Communicating via REST. (best-practice π)
- Symfony tuned for best performance in prod. (performance β‘).
- Opcache php preloading + symfony recommended optimization (performance β‘).
- Php JIT not implemented. JIT increases performance only in high concurrency regime while in low concurrency it is more performant to not use JIT (performance β‘).
- Xdebug setup to debug both html requests and CLI commands.
- Phpstan in a very strict level. Including shipmonk-rules.
- PHP_CodeSniffer in a very strict level (lots of rules are my personal "taste", feel free to change/remove them). Including phpstan-strict-rules and slevomat-coding-standard
- Psalm.
- PHPUnit Unit tests.
- PHPUnit Functional tests (including smoke tests).
- Other linters (Composer, Yaml, Symfony container).
- Php-fpm access proper logging (json format, GCP LogEntry compatible, correct severity, trying to fix https://bugs.php.net/bug.php?id=73886)
- Symfony monolog proper logging (json format, GCP compatible using GoogleCloudLoggingFormatter)
- DDD, TDD and BDD ready. If you want to follow DDD, create new bounded contexts in
api/src/Context
- React framework as a frontend SPA
- Independent on any used backend. Communicating via REST (best-practice π)
- Typescript
- Eslint
- Vite
- React Query and Axios for proper data fetching including caching.
- wsc (should be 20x faster than Babel but see the current caveats).
- Prettier
- DevOps: CI pipeline to build both test and prod images, test them and push prod images to registry
- Run all BE tests from point 2 on final (test) docker image (best-practice π) (Phpstan, CodeSniffer, Psalm, PHPUnit, linters, etc ...).
- Run helm lint and helm dry installation into minikube cluster to ensure real deployment will be without surprises.
- If everything passes there are php and nginx environment agnostic (best-practice π) containers ready to be shipped into any environment (including prod of course).
- Pipeline expects self-hosted GitHub runner(s). See for more information.
- DevOps: CD
- Helm kubernetes deploys main branch to prod and branches starting with feature* to dev environment.
- Platform agnostic. As long as there is a Kubernetes you can use simple config files in
.deploy/helm
dir to deploy to your environment. - Separate pods for nginx and php for better scalability (best-practice π).
- Both nginx and php pods have readiness probes.
- Both nginx and php pods have liveness probes.
- Optional: Ingress to connect your kubernetes cluster with outside world (you can use platform Load Balancer, but it is usually billed).
- Let's Encrypt Certbot.
- Security:
- Secrets are not stored in file system, thus prevent directory traversal attack (best-practice π).
- Secrets are not stored as environment variables, thus prevent any debug or log attacks or misconfigurations (best-practice π).
- Zero trust, the least privilege and giving as minimum as possible information principles used in nginx.conf.
- HTTPS Let's encrypt certificate. And if you use infrastructure-skeleton you receive automated creation of missing or expired certificates with cert-manager.
- HTTP -> HTTPS 301 redirect (only for prod environments, not for eg dev.skeleton.cz).
- Using local dev proxy between FE and BE to correctly handle CORS (and to avoid HOST_URL=localhost). (best-practice π)
- Roave/SecurityAdvisories to prevent using dependencies with known security vulnerabilities.
- CSRF attack prevented.
- XSS attack prevented.
- SQL injection attack prevented (Doctrine capability if you use it correctly).
- etc...
All the following setup information are optional, but highly recommended. It should not take you more than 10 minutes of setup time, and it will save you hours of your time in a future. Senior developer uses all of them. Period.
PHPStan is configured out of the box. For a better DX configure your IDE too. See PHPStan usage for more details.
PHP_CodeSniffer is configured out of the box. For a better DX configure your IDE too. See PHP_CodeSniffer usage for more details.
Xdebug is configured out of the box on the container side. You need to configure your IDE too. See Xdebug usage for more details.
Setup aliases in your shell to start your local dev stack for your everyday use.
For example what I have in my .zshrc
file:
alias dcs="cd ~/Projects/personal/symfony-react-skeleton/.docker/ && docker compose --env-file ../api/.env.local up -d && cd ../fe && pnpm run dev"
alias des='docker exec -it symfony-react-skeleton_php sh -l'
I use dcs
to start complete FE+BE dev environment and des
to docker exec into php container.
Create a global .gitignore
file in a parent directory for your project and add .idea
line in it (.idea is for PhpStorm, if you use another editor, change the directory name accordingly). This directory created by PhpStorm in every project should not be versioned but should not be included in project's scope .gitignore file either (best-practice π).
I know this is not favorite opinion, but if you are serious about SW development and gaining seniority in it, you should have Linux as OS in your development machine. I would recommend dual boot setup. I personally don't think WSL or other similar solutions are the right way.
Use this section for debugging or if you want to see your app running in prod ASAP. Real world deployment should be setup in CD pipeline. This example is configured out-of-the-box for infrastructure-skeleton.
- Provision your infrastructure by using infrastructure-skeleton. Save output values from terraform apply. You will use them in following steps. You can use your own infrastructure, in that case use your own output parameters.
- Change
parameters.application_name:
parameter in api/config/services.yaml. Useapp_name
output from terraform apply. - Build and push your prod images. For this you need
artifact_registry
terraform output.# for {IMAGE_PATH} use {artifact_registry from terraform output}/{myproject} eg.: IMAGE_PATH=europe-west1-docker.pkg.dev/basic4-2542859/all-registry-europe-west1/symfony-react-skeleton IMAGE_PATH={artifact_registry}/{myproject} cd .docker && IMAGE_PATH="${IMAGE_PATH}" docker compose -f docker-compose-prod.yaml build IMAGE_PATH="${IMAGE_PATH}" docker compose -f docker-compose-prod.yaml push
- Edit values.yaml file (use
app_environment
,gcp_project_id
,app_gcp_service_account_name
andapp_k8_service_account_name
outputs from infrastructure terraform apply from point 1.). Don't forget to edit alsohost
which will be your url andletsencryptCertEmail
for Let's encrypt. - Change
name
in .deploy/helm/Chart.yaml for example useapp_name
output from terraform apply. - Deploy your pushed images into k8 cluster created in point 1. For this you need
app_k8_namespace
terraform output. You can choose any string for{helm_release_name}
.K8_NAMESPACE={app_k8_namespace} HELM_RELEASE={helm_release_name} cd ../.deploy/helm helm upgrade $HELM_RELEASE . --create-namespace --install --namespace "${K8_NAMESPACE}" --set image.path="${IMAGE_PATH}"
- Give a nginx ingress approx 5 min to load up and test your running app.
{your_ip}
can be grabbed here or viakubectl get ingress -A
.{host}
is the same you used in point 4 in values.yaml.curl -ivL 'https://{your_ip}/api/status' --header 'Host: {host}' # eg: curl --location --request GET 'http://104.155.113.172/api/status' --header 'Host: skeleton.totea.cz'
I greatly appreciate all suggestions and contributions. Contributions are what make the open source community such an amazing place to learn, inspire, and create.
There are three options how to contribute.
- Open an issue with the tag "enhancement".
- Send me a message via LinkedIn or email and I will grant you write access to this repo. or
- Follow these instructions.
Any options is fine and I am looking forward for your awesome improvements or just typo fix.
- CI: Add linter for helm
- CI: Add dry deploy to k8 as a test
- Helm: Add liveness probe
- Helm: Add readiness probe
- Add static URL as a Live Demo link
- Add OPCache
- Add https certificate
- CI: Push prod images only for main branch
- CI: Use SHA for prod images
- Add CI e2e tests against prod images
- Add Prometheus
- Add Grafana (and disable GCP logs)
- Log deprecations
- Collect and display console logs
- Add Helm tests
- Add architecture schema into README
- Change .docker -> docker (it is more intuitive to have this folder not hidden)
See the open issues for a full list of proposed features (and known issues).
- How to run CI tests locally?
- How to run BE application in prod mode locally?
- Where should I put my new php code?
- How to debug BE?
A developer can run all BE tests at once composer test
or only selected BE test can be ran e.g. composer phpstan
. Commands must be run inside php container.
A developer can run all FE tests at once pnpm run test
or only selected FE test can be ran e.g. pnpm run lint
.
docker exec ${PROJECT_NAME}_php composer test
- Uncomment services.php.environment section in
.docker/docker-compose-prod.yaml
to be able to connect to local DB if needed. -
cd .docker && docker compose -f docker-compose-prod.yaml up -d
- Visit http://localhost:82/api/status.
This skeleton is DDD ready. All code coupled to App (Symfony) should be placed in api/src/App
. E.g. security, authentication, authorization, Entities if you want to use doctrine migrations and/or ORM, Controllers and console Commands. The rest (bounded contexts) should be independent on Symfony framework and placed in api/src/Context/MyBoundedContext
You can use /api/status
endpoint to get some debug information for php-fpm or bin/console status
command to get same information for CLI.
See the LICENSE file for license rights and limitations (MIT).