# GraphQL API Reference The B12 SIS provides a read-only GraphQL API for flexible data querying. This API allows you to fetch exactly the data you need in a single request, with support for filtering, pagination, sorting, and nested relationships. ## Overview | Feature | Description | |---------|-------------| | **Endpoint** | `/graphql` | | **Methods** | POST, GET | | **Authentication** | JWT Bearer token required | | **Operations** | Query only (read-only API) | | **Playground** | `/playground` (development only) | ```{note} The GraphQL API is **read-only**. All create, update, and delete operations should use the REST API. ``` ## Quick Start ### 1. Authentication Include your JWT token and team ID in request headers: ```bash curl -X POST https://api.yourschool.edu/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "X-Team-ID: YOUR_TEAM_ID" \ -d '{"query": "{ students { nodes { id firstName lastName } } }"}' ``` ### 2. Basic Query ```graphql query { students { nodes { id code firstName lastName email status } pageInfo { totalCount totalPages currentPage hasNextPage } } } ``` ### 3. Query with Variables ```graphql query GetStudent($id: ID!) { student(id: $id) { id firstName lastName email currentClass { name code } } } ``` Variables: ```json { "id": "550e8400-e29b-41d4-a716-446655440000" } ``` --- ## Request Headers | Header | Required | Description | |--------|----------|-------------| | `Authorization` | Yes | JWT Bearer token: `Bearer ` | | `X-Team-ID` | Yes | Team/Campus UUID for multi-tenancy | | `Content-Type` | Yes | Must be `application/json` | | `Accept-Language` | No | Language preference (`en`, `vi`) | --- ## Available Queries ### Academic Structure | Query | Single | Description | |-------|--------|-------------| | `academicSessions` | `academicSession(id)` | School years (e.g., 2024-2025) | | `semesters` | `semester(id)` | Terms within academic sessions | | `classes` | `class(id)` | Homeroom classes | | `courses` | `course(id)` | Subject courses | | `subjects` | `subject(id)` | Subject definitions | | `programs` | `program(id)` | Educational programs | | `gradeLevels` | `gradeLevel(id)` | Grade levels (K-12) | | `educationLevels` | `educationLevel(id)` | Education level groupings | ### Personnel | Query | Single | Description | |-------|--------|-------------| | `students` | `student(id)` | Student records | | `teachers` | `teacher(id)` | Teacher/staff records | | `users` | `user(id)` | System user accounts | | `guardians` | `guardian(id)` | Parent/guardian records | ### Assessment & Grading | Query | Single | Description | |-------|--------|-------------| | `gradebooks` | `gradebook(id)` | Semester gradebooks | | `gradebookCourses` | `gradebookCourse(id)` | Course-level grades | | `assessments` | `assessment(id)` | Assignments, tests, quizzes | | `assessmentResults` | `assessmentResult(id)` | Student scores | | `gradeScales` | `gradeScale(id)` | Grading scale definitions | ### Attendance | Query | Single | Description | |-------|--------|-------------| | `attendances` | `attendance(id)` | Daily attendance records | | `attendanceConfigs` | `attendanceConfig(id)` | Attendance status configurations | ### Enrollment | Query | Single | Description | |-------|--------|-------------| | `studentEnrollClasses` | `studentEnrollClass(id)` | Class enrollments | | `studentEnrollCourses` | `studentEnrollCourse(id)` | Course enrollments | ### Timetabling | Query | Single | Description | |-------|--------|-------------| | `timetables` | `timetable(id)` | Timetable definitions | | `periods` | `period(id)` | Class periods | | `rooms` | `room(id)` | Rooms/facilities | ### Communication | Query | Single | Description | |-------|--------|-------------| | `behaviors` | `behavior(id)` | Student behavior records | | `emailTemplates` | `emailTemplate(id)` | Email templates | ### System Configuration | Query | Single | Description | |-------|--------|-------------| | `roles` | `role(id)` | User roles | | `teams` | `team(id)` | Teams/campuses | | `fieldDefs` | `fieldDef(id)` | Dynamic field definitions | | `auditLogs` | `auditLog(id)` | System audit logs | ### Other | Query | Single | Description | |-------|--------|-------------| | `facilities` | `facility(id)` | Campus facilities | | `holidays` | `holiday(id)` | Holiday definitions | | `personRelationships` | `personRelationship(id)` | Guardian-student relationships | | `leads` | `lead(id)` | CRM leads | --- ## Pagination All list queries support pagination via the `pagination` argument. ### Input ```graphql input PaginationInput { page: Int = 1 # Page number (1-indexed) limit: Int = 20 # Items per page (max: 100) } ``` ### Example ```graphql query { students(pagination: { page: 2, limit: 25 }) { nodes { id firstName lastName } pageInfo { totalCount totalPages currentPage hasNextPage hasPreviousPage } } } ``` ### Response ```json { "data": { "students": { "nodes": [...], "pageInfo": { "totalCount": 150, "totalPages": 6, "currentPage": 2, "hasNextPage": true, "hasPreviousPage": true } } } } ``` --- ## Sorting All list queries support sorting via the `sort` argument. ### Input ```graphql input SortInput { field: String! # Field name (camelCase) order: SortOrder = ASC # ASC or DESC } ``` ### Example ```graphql query { students( sort: { field: "createdAt", order: DESC } pagination: { limit: 10 } ) { nodes { id firstName lastName createdAt } } } ``` ### Common Sort Fields | Field | Description | |-------|-------------| | `createdAt` | Record creation date (default) | | `updatedAt` | Last update date | | `name` | Name field | | `code` | Code field | | `status` | Status field | --- ## Filtering The GraphQL API supports complex filtering with AND/OR logic. ### Filter Structure ```graphql input FilterGroup { group: FilterGroupType! # AND or OR conditions: [FilterCondition!] groups: [FilterGroup!] # Nested groups } input FilterCondition { field: String! operator: FilterOperator! value: String # Single value values: [String!] # Multiple values (for IN, NOT_IN) } ``` ### Available Operators | Operator | Description | Example | |----------|-------------|---------| | `EQ` | Equal | `status = "active"` | | `NE` | Not equal | `status != "inactive"` | | `GT` | Greater than | `enrollmentYear > 2020` | | `GTE` | Greater than or equal | `enrollmentYear >= 2020` | | `LT` | Less than | `enrollmentYear < 2024` | | `LTE` | Less than or equal | `enrollmentYear <= 2024` | | `CONTAINS` | Contains substring | `name LIKE "%john%"` | | `STARTS_WITH` | Starts with | `name LIKE "john%"` | | `ENDS_WITH` | Ends with | `name LIKE "%son"` | | `IN` | In list | `status IN ["active", "alumni"]` | | `NOT_IN` | Not in list | `status NOT IN ["withdrawn"]` | | `IS_NULL` | Is null | `deletedAt IS NULL` | | `IS_NOT_NULL` | Is not null | `email IS NOT NULL` | ### Filter Examples #### Simple Filter ```graphql query { students( filter: { group: AND conditions: [ { field: "status", operator: EQ, value: "active" } ] } ) { nodes { id firstName lastName status } } } ``` #### Multiple Conditions (AND) ```graphql query { students( filter: { group: AND conditions: [ { field: "status", operator: EQ, value: "active" } { field: "enrollmentYear", operator: GTE, value: "2020" } ] } ) { nodes { id firstName enrollmentYear } } } ``` #### OR Conditions ```graphql query { students( filter: { group: OR conditions: [ { field: "status", operator: EQ, value: "active" } { field: "status", operator: EQ, value: "alumni" } ] } ) { nodes { id status } } } ``` #### Using IN Operator ```graphql query { students( filter: { group: AND conditions: [ { field: "status", operator: IN, values: ["active", "alumni"] } ] } ) { nodes { id status } } } ``` #### Search by Name (CONTAINS) ```graphql query { students( filter: { group: AND conditions: [ { field: "firstName", operator: CONTAINS, value: "john" } ] } ) { nodes { id firstName lastName } } } ``` #### Nested AND/OR Groups ```graphql query { students( filter: { group: AND conditions: [ { field: "status", operator: EQ, value: "active" } ] groups: [ { group: OR conditions: [ { field: "enrollmentYear", operator: EQ, value: "2023" } { field: "enrollmentYear", operator: EQ, value: "2024" } ] } ] } ) { nodes { id firstName enrollmentYear } } } ``` --- ## Entity Schemas ### Student ```graphql type Student { # Identity id: ID! code: String! moetId: String status: String! # Personal Info firstName: String lastName: String fullNameLastFirst: String fullNameFirstLast: String email: String phone: String avatar: String gender: String religion: String personalIdNumber: String birthDate: Date nationality: String ethnicity: String # Enrollment enrollmentDate: Date enrollmentYear: Int source: String # App/LMS Access appActive: Boolean appUser: String appLastLogin: Time lmsActive: Boolean lmsId: String lmsUser: String # Addresses permanentAddress: Address currentAddress: Address birthPlaceAddress: Address birthRegisAddress: Address # Relationships currentClass: Class currentClassId: ID program: Program programId: ID user: User userId: ID # Collections behaviors: [Behavior!]! attendances: [Attendance!]! gradebooks: [Gradebook!]! studentEnrollClasses: [StudentEnrollClass!]! studentEnrollCourses: [StudentEnrollCourse!]! guardians: [PersonRelationship!]! # Metadata createdAt: Time updatedAt: Time teamId: ID createdBy: User updatedBy: User team: Team } ``` ### Teacher ```graphql type Teacher { # Identity id: ID! code: String! type: String! status: String! # Personal Info firstName: String lastName: String fullNameLastFirst: String fullNameFirstLast: String email: String phone: String avatar: String gender: String religion: String personalIdNumber: String birthDate: Date nationality: String ethnicity: String # Addresses permanentAddress: Address currentAddress: Address # Relationships user: User userId: ID # Collections workExperiences: [WorkExperience!]! certifications: [Certification!]! educationQualifications: [EducationQualification!]! classes: [Class!]! # Metadata createdAt: Time updatedAt: Time teamId: ID createdBy: User updatedBy: User team: Team } ``` ### Class ```graphql type Class { id: ID! name: String description: String code: String! status: String! # Relationships academicSession: AcademicSession academicSessionId: ID gradeLevel: GradeLevel gradeLevelId: ID program: Program programId: ID facility: Facility facilityId: ID primaryTeacher: Teacher primaryTeacherId: ID teacherAssistants: [Teacher!]! studentEnrollClasses: [StudentEnrollClass!]! timetables: [Timetable!]! # Computed studentCount: Int # Metadata createdAt: Time updatedAt: Time teamId: ID createdBy: User updatedBy: User team: Team } ``` ### Course ```graphql type Course { id: ID! name: String description: String code: String! shortName: String status: String! type: String! # Canvas Integration canvasId: String needCanvasSync: Boolean # Relationships academicSession: AcademicSession academicSessionId: ID program: Program programId: ID educationLevel: EducationLevel educationLevelId: ID subject: Subject subjectId: ID semester: Semester semesterId: ID primaryTeacher: Teacher primaryTeacherId: ID teacherAssistants: [Teacher!]! studentEnrollCourses: [StudentEnrollCourse!]! assessments: [Assessment!]! # Computed studentCount: Int # Metadata createdAt: Time updatedAt: Time teamId: ID createdBy: User updatedBy: User team: Team } ``` ### Assessment ```graphql type Assessment { id: ID! name: String description: String title: String! type: String! category: String weight: Float totalPoints: Float! dueDate: Time assessmentDate: Time isPublished: Boolean! allowLateSubmission: Boolean latePenalty: Float instructions: String rubric: JSON isLocked: Boolean! noShowReport: Boolean # Canvas Integration canvasAssignmentId: String canvasAssignmentGroupId: String canvasAssignmentGroupName: String canvasGroupWeight: Float omitFromFinalGrade: Boolean # Relationships course: Course courseId: ID gradeScale: GradeScale gradeScaleId: ID assessmentResults: [AssessmentResult!]! # Metadata createdAt: Time updatedAt: Time teamId: ID createdBy: User updatedBy: User team: Team } ``` ### Gradebook ```graphql type Gradebook { id: ID! name: String description: String overallScore: Float overallGrade: String gpa: Float totalCredits: Float earnedCredits: Float status: String! isPublished: Boolean! publishedAt: Time comments: String lastCalculatedAt: Time lastGenReportAt: Time reportCardFile: String isLocked: Boolean! # Relationships student: Student studentId: ID semester: Semester semesterId: ID academicSession: AcademicSession academicSessionId: ID gradebookCourses: [GradebookCourse!]! # Metadata createdAt: Time updatedAt: Time teamId: ID createdBy: User updatedBy: User team: Team } ``` ### Behavior ```graphql type Behavior { id: ID! name: String description: String type: String! category: String points: Int date: Date time: Time notes: String isNotified: Boolean notifiedAt: Time # Relationships student: Student studentId: ID students: [Student!]! # Many-to-many for group behaviors academicSession: AcademicSession academicSessionId: ID class: Class classId: ID emailTemplate: EmailTemplate emailTemplateId: ID # Metadata createdAt: Time updatedAt: Time teamId: ID createdBy: User updatedBy: User team: Team } ``` --- ## Complex Query Examples ### Dashboard Statistics ```graphql query Dashboard { students(filter: { group: AND, conditions: [{ field: "status", operator: EQ, value: "active" }] }) { pageInfo { totalCount } } teachers(filter: { group: AND, conditions: [{ field: "status", operator: EQ, value: "working" }] }) { pageInfo { totalCount } } classes { pageInfo { totalCount } } courses { pageInfo { totalCount } } } ``` ### Student with Full Details ```graphql query GetStudentDetails($id: ID!) { student(id: $id) { id code firstName lastName email phone status enrollmentYear birthDate gender currentClass { id name code gradeLevel { name } } program { id name } guardians { id relationship isPrimary } gradebooks { id overallScore overallGrade semester { name } } } } ``` ### Class Roster with Students ```graphql query GetClassRoster($classId: ID!) { class(id: $classId) { id name code primaryTeacher { firstName lastName } gradeLevel { name } studentEnrollClasses { student { id code firstName lastName email status } enrollmentDate status } } } ``` ### Course with Assessments and Results ```graphql query GetCourseAssessments($courseId: ID!) { course(id: $courseId) { id name code primaryTeacher { firstName lastName } assessments { id title type totalPoints dueDate isPublished assessmentResults { student { id firstName lastName } score grade submittedAt } } } } ``` ### Teachers with Their Classes and Courses ```graphql query GetTeacherSchedule($teacherId: ID!) { teacher(id: $teacherId) { id firstName lastName email classes { id name code gradeLevel { name } studentCount } } courses( filter: { group: AND conditions: [ { field: "primaryTeacherId", operator: EQ, value: $teacherId } ] } ) { nodes { id name code subject { name } studentCount } } } ``` ### Attendance Report ```graphql query GetAttendanceReport($classId: ID!, $startDate: String!, $endDate: String!) { attendances( filter: { group: AND conditions: [ { field: "classId", operator: EQ, value: $classId } { field: "date", operator: GTE, value: $startDate } { field: "date", operator: LTE, value: $endDate } ] } sort: { field: "date", order: ASC } ) { nodes { id date status checkInTime checkOutTime notes student { id code firstName lastName } } pageInfo { totalCount } } } ``` ### Gradebook Report ```graphql query GetGradebookReport($semesterId: ID!) { gradebooks( filter: { group: AND conditions: [ { field: "semesterId", operator: EQ, value: $semesterId } { field: "isPublished", operator: EQ, value: "true" } ] } sort: { field: "overallScore", order: DESC } ) { nodes { id overallScore overallGrade gpa student { id code firstName lastName currentClass { name } } gradebookCourses { score grade course { name subject { name } } } } pageInfo { totalCount } } } ``` --- ## Using GraphQL Clients ### JavaScript (fetch) ```javascript async function graphqlQuery(query, variables = {}) { const response = await fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}`, 'X-Team-ID': localStorage.getItem('teamId') }, body: JSON.stringify({ query, variables }) }); const result = await response.json(); if (result.errors) { throw new Error(result.errors[0].message); } return result.data; } // Usage const data = await graphqlQuery(` query GetStudents($limit: Int!) { students(pagination: { limit: $limit }) { nodes { id firstName lastName } pageInfo { totalCount } } } `, { limit: 10 }); ``` ### Angular (Apollo Angular) ```typescript import { Apollo, gql } from 'apollo-angular'; @Injectable() export class StudentService { constructor(private apollo: Apollo) {} getStudents(limit: number) { return this.apollo.watchQuery({ query: gql` query GetStudents($limit: Int!) { students(pagination: { limit: $limit }) { nodes { id firstName lastName } pageInfo { totalCount } } } `, variables: { limit } }).valueChanges; } } ``` ### cURL ```bash # Simple query curl -X POST https://api.yourschool.edu/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "X-Team-ID: YOUR_TEAM_ID" \ -d '{"query": "{ students(pagination: { limit: 5 }) { nodes { id firstName lastName } } }"}' # Query with variables curl -X POST https://api.yourschool.edu/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "X-Team-ID: YOUR_TEAM_ID" \ -d '{ "query": "query GetStudent($id: ID!) { student(id: $id) { id firstName lastName email } }", "variables": {"id": "550e8400-e29b-41d4-a716-446655440000"} }' ``` --- ## GraphQL Playground Access the interactive GraphQL IDE at `/playground` (development mode only). ### Features - Auto-complete for queries - Schema documentation browser - Query history - Variables panel - Headers configuration ### Enabling Playground Set `DEV_MODE=true` in your environment to enable the playground. ```{warning} The playground is disabled in production for security reasons. ``` --- ## Error Handling ### Error Response Format ```json { "errors": [ { "message": "record not found", "path": ["student"], "extensions": { "code": "NOT_FOUND" } } ], "data": { "student": null } } ``` ### Common Error Codes | Code | Description | |------|-------------| | `UNAUTHENTICATED` | Missing or invalid JWT token | | `FORBIDDEN` | Insufficient permissions | | `NOT_FOUND` | Resource not found | | `BAD_REQUEST` | Invalid query or variables | | `INTERNAL_ERROR` | Server error | --- ## Best Practices ### 1. Request Only What You Need ```graphql # Good - Only request needed fields query { students { nodes { id firstName lastName } } } # Avoid - Requesting all fields unnecessarily query { students { nodes { id code firstName lastName email phone avatar # ... many more fields } } } ``` ### 2. Use Pagination for Large Datasets ```graphql query { students(pagination: { page: 1, limit: 50 }) { nodes { id firstName lastName } pageInfo { totalCount hasNextPage } } } ``` ### 3. Use Variables for Dynamic Values ```graphql # Good - Using variables query GetStudentsByStatus($status: String!) { students( filter: { group: AND conditions: [{ field: "status", operator: EQ, value: $status }] } ) { nodes { id firstName } } } ``` ### 4. Batch Related Queries ```graphql # Good - Single request for multiple related data query Dashboard { students { pageInfo { totalCount } } teachers { pageInfo { totalCount } } classes { pageInfo { totalCount } } } ``` ### 5. Use Fragments for Reusable Fields ```graphql fragment StudentBasicInfo on Student { id code firstName lastName email status } query { students { nodes { ...StudentBasicInfo } } } ``` --- ## Rate Limiting The GraphQL API shares rate limits with the REST API: - **100 requests per minute** per user - **1000 requests per hour** per user Exceeding limits returns a `429 Too Many Requests` response. --- ## Multi-tenancy Data is automatically filtered by team: 1. `X-Team-ID` header identifies the current team 2. All queries filter by team and child teams 3. Users only see data they have permission to access --- ## Comparison: GraphQL vs REST API | Feature | GraphQL | REST API | |---------|---------|----------| | **Use Case** | Flexible data fetching | CRUD operations | | **Operations** | Read-only (Query) | Create, Read, Update, Delete | | **Response** | Exactly what you request | Fixed structure | | **Multiple Resources** | Single request | Multiple requests | | **Filtering** | Built-in with operators | POST body filter | | **Nested Data** | Native support | Preloads parameter | **Recommendation**: Use GraphQL for complex read queries and dashboards. Use REST API for all write operations.