A data model gives application data a shape at each boundary: database rows, Rust values, and HTTP payloads. In a course service, a `Course` struct represents the fields read from PostgreSQL and used by handlers. Its fields need to agree with the table columns and their types. section 4.2.2 section 4.3.2
/* Drop table if it already exists*/ drop table if exists ezy_course_c4; /* Create a table. */ /* Note: Don't put a comma after last field */ create table ezy_course_c4 ( course_id serial primary key, tutor_id INT not null, course_name varchar(140) not null, posted_time TIMESTAMP default now() ); /* Load seed data for testing */ insert into ezy_course_c4 (course_id,tutor_id, course_name,posted_time) values(1, 1, 'First course', '2020-12-17 05:40:00'); insert into ezy_course_c4 (course_id, tutor_id, course_name,posted_time) values(2, 1, 'Second course', '2020-12-18 05:45:00');
The initial `ezy_course_c4` table defines a primary key, a tutor ID, a required course name, and a timestamp with a database default. Its seed rows provide data for testing queries. section 4.2.2
Listing 4.2 Data model for the tutor web service
use actix_web::web;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Course { #1
pub course_id: i32,
pub tutor_id: i32,
pub course_name: String,
pub posted_time: Option<NaiveDateTime>,
}
impl From<web::Json<Course>> for Course { #2
fn from(course: web::Json<Course>) -> Self {
Course {
course_id: course.course_id,
tutor_id: course.tutor_id,
course_name: course.course_name.clone(),
posted_time: course.posted_time,
}
}
}
In `src/iter2/models.rs`, `Course` uses `i32` IDs, a `String` name, and `Option<NaiveDateTime>` for the timestamp. Serde derives support serialization and deserialization; the `From<web::Json<Course>>` implementation converts an Actix JSON payload into the application struct. section 4.3.2
As the service grows, keep the model aligned with what each operation accepts and returns:
- Use separate structures for creation and updating when those operations require different fields. `Option<T>` marks optional Rust fields, with corresponding database column definitions. section 6.4
- Map query results into structs with `sqlx::query_as!` where suitable, or map records manually. Keep database access and handlers in separate modules from model definitions. section 6.4
- Extend the schema and APIs together: tutor records add tutor models and tables, while foreign-key constraints define the relationship between tutors and courses. Update handlers, database functions, and tests alongside the model. section 6.3 section 6.4
Listing 8.1 Data model
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct TutorRegisterForm { #1
pub username: String,
pub password: String,
pub confirmation: String,
pub name: String,
pub imageurl: String,
pub profile: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct TutorResponse { #2
pub tutor_id: i32,
pub tutor_name: String,
pub tutor_pic_url: String,
pub tutor_profile: String,
}
#[derive(Serialize, Deserialize, Debug, sqlx::FromRow)]
pub struct User { #3
pub username: String,
pub tutor_id: Option<i32>,
pub user_password: String,
}
A registration frontend uses distinct shapes for a submitted form, a tutor response, and a locally stored user. `User` derives `sqlx::FromRow`, while `tutor_id` is optional—one struct need not serve every boundary. section 8.1






















