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.
ConcernAngular handlesRails handles
RoutingClient-side SPA routingAPI endpoint routing
StateComponent state, NgRxDatabase, Active Record
ValidationReactive forms, custom validatorsStrong parameters, model validations
AuthHTTP interceptors, guardsDevise/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 rbenv or RVM)
  • Rails 7+ (install with gem install rails)
  • PostgreSQL 13+ for robust data handling
  • rack-cors gem 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 rbenv for Ruby and nvm for Node.js to avoid version conflicts
  • Update database.yml with encoding: unicode
  • Add rack-cors to your Gemfile and run bundle 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-cors allows 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 api to 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.