Most teams pick Angular for its TypeScript-first frontend and Rails for its productive API backend. The challenge is wiring them together without CORS headaches, proxy misconfigurations, or deployment surprises. This guide walks through the full integration from empty directories to a working CRUD app.
Why pair Angular with Rails?
Angular and Rails solve complementary problems:
- Angular provides a structured, TypeScript-driven frontend with dependency injection, reactive forms, and a powerful CLI.
- Rails API mode strips away views, helpers, and assets, giving you a lightweight JSON backend with convention-driven routing.
- Team independence: frontend and backend teams deploy separately, share only an API contract.
| Concern | Angular handles | Rails handles |
|---|---|---|
| Routing | Client-side SPA routing | API endpoint routing |
| State | Component state, NgRx | Database, Active Record |
| Validation | Reactive forms, custom validators | Strong parameters, model validations |
| Auth | HTTP interceptors, guards | Devise/JWT token generation |
Build a FullStack CRUD App (Rails + Angular) - Beginner Tutorial
What do you need before starting?
Backend requirements
- Ruby 3.0+ (manage with
rbenvorRVM) - Rails 7+ (install with
gem install rails) - PostgreSQL 13+ for robust data handling
rack-corsgem for cross-origin API communication
Frontend requirements
- Node.js 16+ (manage with
nvm) - Angular CLI 16+ (
npm install -g @angular/cli) - A code editor with Angular Language Service and Ruby extensions
Environment configuration
Store credentials in .env at the Rails project root. Add eval "$(rbenv init -)" to your shell profile. Key steps:
- Use
rbenvfor Ruby andnvmfor Node.js to avoid version conflicts - Update
database.ymlwithencoding: unicode - Add
rack-corsto your Gemfile and runbundle install - Generate self-signed SSL certificates for testing secure features
How to build the Rails API backend
Generate the API app
rails new my_angular_rails_api --api --database=postgresql
cd my_angular_rails_api
The --api flag creates a lightweight app without views or assets. Generate your first model:
rails generate model Product name:string price:decimal description:text
Edit the migration to set precision: 8, scale: 2 on the price column before running rails db:migrate.
Configure CORS
Add rack-cors to your Gemfile, then configure config/initializers/cors.rb:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:4200'
resource '*',
headers: %w[Accept Authorization Content-Type Origin X-Requested-With Accept-Language],
methods: [:get, :post, :put, :patch, :delete, :options]
end
end
The Accept-Language header lets Angular send locale data so Rails can return properly formatted responses.
Seed sample data
Create a products controller and seed data:
rails generate controller Api::Products
Add seed records in db/seeds.rb with realistic prices, then run rails db:seed. Test at http://localhost:3000/api/products.
How to set up the Angular frontend
Create the project
ng new my-angular-frontend
cd my-angular-frontend
ng generate service services/product
Select Yes for routing and CSS for stylesheets when prompted.
Configure the API proxy
Create proxy.conf.json in your Angular project root:
{
"/api/*": {
"target": "http://localhost:3000",
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
}
}
Update angular.json under the serve section:
"proxyConfig": "proxy.conf.json"
This routes all /api/ requests to Rails on port 3000, eliminating CORS issues during development.
Connect Angular to the API
Create a product interface and implement the service:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class ProductService {
private apiUrl = '/api/products';
constructor(private http: HttpClient) {}
getAllProducts(): Observable<any[]> {
return this.http.get<any[]>(this.apiUrl);
}
getProduct(id: number): Observable<any> {
return this.http.get<any>(`${this.apiUrl}/${id}`);
}
createProduct(product: any): Observable<any> {
return this.http.post<any>(this.apiUrl, product);
}
updateProduct(id: number, product: any): Observable<any> {
return this.http.put<any>(`${this.apiUrl}/${id}`, product);
}
deleteProduct(id: number): Observable<any> {
return this.http.delete<any>(`${this.apiUrl}/${id}`);
}
}
How to implement CRUD operations
Rails API endpoints
Define standard CRUD actions in app/controllers/api/products_controller.rb:
class Api::ProductsController < Api::ApplicationController
def index
render json: Product.all
end
def show
render json: Product.find(params[:id])
end
def create
product = Product.create!(product_params)
render json: product, status: :created
end
def update
product = Product.find(params[:id])
product.update!(product_params)
render json: product
end
def destroy
Product.find(params[:id]).destroy
head :no_content
end
private
def product_params
params.require(:product).permit(:name, :price, :description, :category)
end
end
Set up routes in config/routes.rb:
Rails.application.routes.draw do
namespace :api do
resources :products
end
end
For API controllers, bypass CSRF by inheriting from ActionController::API:
class Api::ApplicationController < ActionController::API
end
Angular component for data management
Generate the component and implement CRUD logic:
ng generate component components/product-manager
export class ProductManagerComponent implements OnInit {
products: any[] = [];
selectedProduct: any = {};
isEditing = false;
constructor(private productService: ProductService) {}
ngOnInit(): void {
this.loadProducts();
}
loadProducts(): void {
this.productService.getAllProducts().subscribe({
next: (data) => this.products = data,
error: (error) => console.error('Error loading products:', error)
});
}
saveProduct(): void {
const action = this.isEditing
? this.productService.updateProduct(this.selectedProduct.id, this.selectedProduct)
: this.productService.createProduct(this.selectedProduct);
action.subscribe({
next: () => { this.loadProducts(); this.resetForm(); },
error: (error) => console.error('Error saving product:', error)
});
}
deleteProduct(id: number): void {
this.productService.deleteProduct(id).subscribe({
next: () => this.loadProducts(),
error: (error) => console.error('Error deleting product:', error)
});
}
resetForm(): void {
this.selectedProduct = {};
this.isEditing = false;
}
}
How to test and deploy the integration
Testing strategy
Write unit tests for Rails API endpoints:
RSpec.describe Api::ProductsController, type: :controller do
describe "GET #index" do
it "returns products as JSON" do
Product.create(name: "Widget", price: 29.99)
get :index
expect(response).to have_http_status(200)
expect(JSON.parse(response.body).size).to eq(1)
end
end
end
Write integration tests for Angular components using Angular’s testing tools to simulate user actions and validate data presentation.
Run tests with bundle exec rspec (Rails) and ng test (Angular). Both suites should pass before deployment.
Fixing common integration problems
- CORS errors: Verify
rack-corsallows Angular’s origin. Check browser console for “Access to XMLHttpRequest blocked.” - Preflight failures: Ensure your API handles OPTIONS requests. Rails API mode handles this automatically with
rack-cors. - Route mismatches: Run
rails routes | grep apito verify endpoints match Angular service URLs. - Data format conflicts: Return consistent JSON structures. Handle null values with safe navigation (
&.).
Local deployment
Build Angular for production and serve through Rails:
ng build --configuration production
cp -r dist/your-app-name/* ../my_angular_rails_api/public/
Add a fallback route in Rails:
get '*path', to: 'application#fallback_index_html',
constraints: ->(req) { !req.xhr? && req.format.html? }
Start Rails with rails server and test at http://localhost:3000.
Practical Implementation: The USEO Approach
At USEO, we have integrated Angular frontends with Rails backends across multiple client projects. Here are patterns we rely on in production:
API versioning from day one. We namespace all endpoints under /api/v1/ even for initial releases. When breaking changes are needed, we spin up /api/v2/ while keeping the old version alive. This costs nothing upfront but saves painful migrations later.
Shared TypeScript types from Rails serializers. We auto-generate TypeScript interfaces from our Rails serializers using a custom Rake task. This eliminates the “frontend says one thing, backend returns another” class of bugs. The generated types go into a shared types/ directory that Angular imports directly.
Contract testing over integration testing. Instead of running full Angular-to-Rails integration tests (which are slow and flaky), we use Pact for consumer-driven contract tests. Angular defines what it expects from each endpoint, Rails verifies it can fulfill those contracts. Tests run in seconds, not minutes.
Deployment as separate services. We deploy Angular to a CDN (Cloudflare Pages or S3+CloudFront) and Rails to a container service. The Angular app hits Rails through an API gateway. This gives independent scaling and deployment cycles. Serving Angular through Rails’ public/ directory works for small projects, but separating them pays off quickly as the team grows.
Error boundary pattern. Every Angular HTTP interceptor logs structured errors to a shared error tracking service (Sentry). Rails controllers use rescue_from to return consistent error JSON. Both sides speak the same error format, so debugging production issues requires looking at one log stream, not two.
FAQs
What advantages does combining Angular with Rails offer?
Angular provides a structured, TypeScript-first frontend with strong tooling. Rails simplifies backend API development through conventions. Together, they create a clean separation of concerns where frontend and backend teams work independently. The combination is widely used for e-commerce, SaaS platforms, and enterprise internal tools.
What are the main challenges when integrating Angular with Rails?
The biggest friction points are CORS configuration, managing two separate development servers, and keeping data contracts in sync. Use rack-cors for cross-origin requests, Angular’s proxy for development, and contract tests to verify API compatibility. Version management (Ruby via rbenv, Node via nvm) prevents environment conflicts.
Should I serve Angular through Rails or deploy them separately?
For small projects or prototypes, serving Angular’s build output through Rails’ public/ directory is the simplest path. For production applications with multiple developers, deploy them as separate services. Angular on a CDN gives better load times and independent deployments. Rails as a standalone API scales more predictably.