<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<meta charset="UTF-8">
<title>Your First SCIM Server - SCIM Server Guide</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<link rel="stylesheet" id="highlight-css" href="../highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="../tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="../ayu-highlight.css">
<script>
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "navy";
window.path_to_searchindex_js = "../searchindex.js";
</script>
<script src="../toc.js"></script>
</head>
<body>
<div id="mdbook-help-container">
<div id="mdbook-help-popup">
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
<div>
<p>Press <kbd>←</kbd> or <kbd>→</kbd> to navigate between chapters</p>
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
<p>Press <kbd>?</kbd> to show this help</p>
<p>Press <kbd>Esc</kbd> to hide this help</p>
</div>
</div>
</div>
<div id="body-container">
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
sidebar_toggle.checked = false;
}
if (sidebar === 'visible') {
sidebar_toggle.checked = true;
} else {
html.classList.remove('sidebar-visible');
}
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">SCIM Server Guide</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<div class="search-wrapper">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
<div class="spinner-wrapper">
<i class="fa fa-spinner fa-spin"></i>
</div>
</div>
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="your-first-scim-server"><a class="header" href="#your-first-scim-server">Your First SCIM Server</a></h1>
<p>Learn to build a working SCIM server in 10 minutes using this library.</p>
<h2 id="quick-start"><a class="header" href="#quick-start">Quick Start</a></h2>
<h3 id="1-create-a-new-project"><a class="header" href="#1-create-a-new-project">1. Create a New Project</a></h3>
<pre><code class="language-bash">cargo new my-scim-server
cd my-scim-server
</code></pre>
<h3 id="2-add-dependencies"><a class="header" href="#2-add-dependencies">2. Add Dependencies</a></h3>
<pre><code class="language-toml">[dependencies]
scim-server = "0.3.11"
tokio = { version = "1.0", features = ["full"] }
serde_json = "1.0"
</code></pre>
<h3 id="3-basic-server-15-lines"><a class="header" href="#3-basic-server-15-lines">3. Basic Server (15 lines)</a></h3>
<pre><pre class="playground"><code class="language-rust">use scim_server::{
providers::StandardResourceProvider,
storage::InMemoryStorage,
RequestContext,
};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create storage and provider - the foundation of your SCIM server
let storage = InMemoryStorage::new(); // Simple storage for development
let provider = StandardResourceProvider::new(storage); // Main SCIM interface
// Create a single-tenant request context - tracks this operation for logging
let context = RequestContext::new("demo".to_string());
let user_data = json!({
"userName": "john.doe",
"emails": [{"value": "john@example.com", "primary": true}],
"active": true
});
let user = provider.create_resource("User", user_data, &context).await?;
println!("Created user: {}", user.get_username().unwrap());
Ok(())
}</code></pre></pre>
<h3 id="4-run-it"><a class="header" href="#4-run-it">4. Run It</a></h3>
<pre><code class="language-bash">cargo run
# Output: Created user: john.doe
</code></pre>
<h2 id="core-operations"><a class="header" href="#core-operations">Core Operations</a></h2>
<h3 id="setup"><a class="header" href="#setup">Setup</a></h3>
<p>For the following examples, we'll use this provider and context setup:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>use scim_server::{
providers::StandardResourceProvider,
storage::InMemoryStorage,
RequestContext,
};
use serde_json::json;
// Create storage and provider - the foundation of your SCIM server
let storage = InMemoryStorage::new(); // Simple storage for development
let provider = StandardResourceProvider::new(storage); // Main SCIM interface
// Single-tenant RequestContext tracks each operation for logging
let context = RequestContext::new("demo".to_string());
<span class="boring">}</span></code></pre></pre>
<p>All the following examples will use these <code>provider</code> and <code>context</code> variables.</p>
<h3 id="creating-resources"><a class="header" href="#creating-resources">Creating Resources</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Use JSON to define user attributes following SCIM 2.0 schema
let user_data = json!({
"userName": "alice.smith",
"name": {
"givenName": "Alice",
"familyName": "Smith"
},
"emails": [{"value": "alice@company.com", "primary": true}],
"active": true
});
// Create the user - provider handles validation and storage
let user = provider.create_resource("User", user_data, &context).await?;
let user_id = user.get_id().unwrap(); // Get the auto-generated unique ID
<span class="boring">}</span></code></pre></pre>
<h3 id="reading-resources"><a class="header" href="#reading-resources">Reading Resources</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Get user by ID - returns Option<Resource> (None if not found)
let retrieved_user = provider.get_resource("User", &user_id, &context).await?;
if let Some(user) = retrieved_user {
println!("Found: {}", user.get_username().unwrap());
}
// Search by specific attribute value - useful for username lookups
let found_user = provider
.find_resource_by_attribute("User", "userName", &json!("alice.smith"), &context)
.await?;
<span class="boring">}</span></code></pre></pre>
<h3 id="updating-resources"><a class="header" href="#updating-resources">Updating Resources</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Updates require the full resource data, including the ID
let update_data = json!({
"id": user_id,
"userName": "alice.smith",
"name": {
"givenName": "Alice",
"familyName": "Johnson" // Changed surname
},
"emails": [{"value": "alice@company.com", "primary": true}],
"active": false // Deactivated
});
// Update replaces the entire resource with new data
let updated_user = provider
.update_resource("User", &user_id, update_data, &context)
.await?;
<span class="boring">}</span></code></pre></pre>
<h3 id="listing-and-searching"><a class="header" href="#listing-and-searching">Listing and Searching</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// List all users - None means no pagination/filtering
let all_users = provider.list_resources("User", None, &context).await?;
println!("Total users: {}", all_users.len());
// Efficiently check existence without retrieving full data
let exists = provider.resource_exists("User", &user_id, &context).await?;
println!("User exists: {}", exists);
<span class="boring">}</span></code></pre></pre>
<h3 id="validation-and-error-handling"><a class="header" href="#validation-and-error-handling">Validation and Error Handling</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// The provider automatically validates data against SCIM schemas
let invalid_user = json!({
"userName": "", // Empty username - violates SCIM requirements
"emails": [{"value": "not-an-email"}], // Invalid email format
});
// Always handle validation errors gracefully
match provider.create_resource("User", invalid_user, &context).await {
Ok(user) => println!("User created: {}", user.get_id().unwrap()),
Err(e) => println!("Validation failed: {}", e), // Detailed error message
}
<span class="boring">}</span></code></pre></pre>
<h3 id="deleting-resources"><a class="header" href="#deleting-resources">Deleting Resources</a></h3>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Delete a resource by ID
provider.delete_resource("User", &user_id, &context).await?;
// Verify deletion
let exists = provider.resource_exists("User", &user_id, &context).await?;
println!("User still exists: {}", exists); // Should be false
<span class="boring">}</span></code></pre></pre>
<h2 id="working-with-groups"><a class="header" href="#working-with-groups">Working with Groups</a></h2>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Groups can contain users as members - useful for access control
// Create a group (assuming you have a user_id from previous examples)
let group_data = json!({
"displayName": "Engineering Team", // Required: human-readable name
"members": [ // Optional: list of member references
{
"value": user_id, // Reference to the user's ID
"$ref": format!("https://example.com/v2/Users/{}", user_id), // Full URI
"type": "User" // Type of the referenced resource
}
]
});
// Using the context and user_id from previous examples
// Create group just like users - same provider interface
let group = provider.create_resource("Group", group_data, &context).await?;
println!("Created group: {}", group.get_attribute("displayName").unwrap());
<span class="boring">}</span></code></pre></pre>
<h2 id="multi-tenant-support"><a class="header" href="#multi-tenant-support">Multi-Tenant Support</a></h2>
<p>For multi-tenant scenarios, you create explicit tenant contexts instead of using the default single-tenant setup:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Import TenantContext for multi-tenant operations
use scim_server::resource::TenantContext;
// Create the same provider as before
let storage = InMemoryStorage::new();
let provider = StandardResourceProvider::new(storage);
// Multi-tenant contexts - each gets isolated data space
let tenant_a = TenantContext::new("company-a".to_string(), "client-123".to_string());
let tenant_a_context = RequestContext::with_tenant("req-a".to_string(), tenant_a);
let tenant_b = TenantContext::new("company-b".to_string(), "client-456".to_string());
let tenant_b_context = RequestContext::with_tenant("req-b".to_string(), tenant_b);
// Same provider, different tenants - data is completely isolated
provider.create_resource("User", user_data.clone(), &tenant_a_context).await?;
provider.create_resource("User", user_data, &tenant_b_context).await?;
// Each tenant sees only their own data
let tenant_a_users = provider.list_resources("User", None, &tenant_a_context).await?;
let tenant_b_users = provider.list_resources("User", None, &tenant_b_context).await?;
println!("Company A users: {}", tenant_a_users.len());
println!("Company B users: {}", tenant_b_users.len());
<span class="boring">}</span></code></pre></pre>
<h2 id="provider-statistics"><a class="header" href="#provider-statistics">Provider Statistics</a></h2>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// Useful for monitoring and debugging your SCIM server
let stats = provider.get_stats().await;
println!("Total tenants: {}", stats.tenant_count); // Number of active tenants
println!("Total resources: {}", stats.total_resources); // Users + Groups + etc.
println!("Resource types: {:?}", stats.resource_types); // ["User", "Group", ...]
<span class="boring">}</span></code></pre></pre>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<ul>
<li><strong><a href="../http/overview.html">HTTP Server Integration</a></strong> - Add REST endpoints with Axum or Actix</li>
<li><strong><a href="../multi-tenant/basics.html">Multi-tenant Setup</a></strong> - Advanced tenant isolation and management</li>
<li><strong><a href="../advanced/overview.html">Advanced Features</a></strong> - Groups, custom schemas, bulk operations</li>
<li><strong><a href="../storage/overview.html">Storage Backends</a></strong> - PostgreSQL, SQLite, and custom storage</li>
</ul>
<h2 id="complete-examples"><a class="header" href="#complete-examples">Complete Examples</a></h2>
<p>See the <a href="../../../../examples/">examples directory</a> for full working implementations:</p>
<ul>
<li><strong><a href="../../../../examples/basic_usage.rs">basic_usage.rs</a></strong> - Complete CRUD operations</li>
<li><strong><a href="../../../../examples/group_example.rs">group_example.rs</a></strong> - Group management with members</li>
<li><strong><a href="../../../../examples/multi_tenant_example.rs">multi_tenant_example.rs</a></strong> - Tenant isolation patterns</li>
</ul>
<h2 id="running-examples"><a class="header" href="#running-examples">Running Examples</a></h2>
<pre><code class="language-bash"># Run any example to see it in action
cargo run --example basic_usage
cargo run --example group_example
</code></pre>
<h2 id="key-concepts"><a class="header" href="#key-concepts">Key Concepts</a></h2>
<ul>
<li><strong><code>StandardResourceProvider</code></strong> - Main interface for SCIM operations</li>
<li><strong><code>InMemoryStorage</code></strong> - Simple storage backend for development</li>
<li><strong><code>RequestContext</code></strong> - Request tracking and tenant isolation</li>
<li><strong>Resource Types</strong> - "User", "Group", or custom types</li>
<li><strong>JSON Data</strong> - All resource data uses <code>serde_json::Value</code></li>
</ul>
<p>You now have a working SCIM server! The examples above demonstrate all core functionality needed for most SCIM implementations.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<a rel="prev" href="../getting-started/installation.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../getting-started/mcp-server.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../getting-started/installation.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../getting-started/mcp-server.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
</div>
</body>
</html>