GraphQLミューテーションまたはREST APIを使用してBlueにファイルをアップロードするための完全ガイド
概要
このガイドでは、Blueにファイルをアップロードするための2つの異なるアプローチを示します:
- 直接GraphQLアップロード(推奨) - 256MBのファイルサイズ制限のあるシンプルな1ステップのアップロード
- REST APIアップロード - 最大4.8GBの大きなファイルをサポートする3ステッププロセス
これは2つの方法の比較です:
機能 | GraphQLアップロード | REST APIアップロード |
---|---|---|
Complexity | Simple (one request) | Complex (three steps) |
File Size Limit | 256MB per file | 4.8GB per file |
Batch Upload | Up to 10 files | Single file only |
Implementation | Direct mutation | Multi-step process |
Best For | Most use cases | Large files only |
GraphQLファイルアップロード
GraphQLアップロードメソッドは、単一のリクエストでファイルをアップロードするシンプルで直接的な方法を提供します。
uploadFile
単一のファイルをファイルストレージシステムにアップロードし、データベースにファイル参照を作成します。
入力:
file: Upload!
- アップロードするファイル(multipart/form-dataを使用)projectId: String!
- ファイルが保存されるプロジェクトIDまたはスラグcompanyId: String!
- ファイルが保存される会社IDまたはスラグ
返却: File!
- 作成されたファイルオブジェクト
例:
mutation UploadFile($input: UploadFileInput!) {
uploadFile(input: $input) {
id
uid
name
size
type
extension
shared
createdAt
project {
id
name
}
folder {
id
title
}
}
}
uploadFiles
複数のファイルをファイルストレージシステムにアップロードし、データベースにファイル参照を作成します。
入力:
files: [Upload!]!
- アップロードするファイルの配列(最大10)projectId: String!
- ファイルが保存されるプロジェクトIDまたはスラグcompanyId: String!
- ファイルが保存される会社IDまたはスラグ
返却: [File!]!
- 作成されたファイルオブジェクトの配列
例:
mutation UploadFiles($input: UploadFilesInput!) {
uploadFiles(input: $input) {
id
uid
name
size
type
extension
shared
createdAt
}
}
クライアント実装
Apollo Client (JavaScript)
単一ファイルアップロード:
import { gql } from '@apollo/client';
const UPLOAD_FILE = gql`
mutation UploadFile($input: UploadFileInput!) {
uploadFile(input: $input) {
id
uid
name
size
type
extension
shared
createdAt
}
}
`;
// Using file input
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const { data } = await uploadFile({
variables: {
input: {
file: file,
projectId: "project_123", // or "my-project-slug"
companyId: "company_456" // or "my-company-slug"
}
}
});
@@CB##91468c8b-036f-467a-aa4c-141e490704a2##CB@@javascript
const UPLOAD_FILES = gql`
mutation UploadFiles($input: UploadFilesInput!) {
uploadFiles(input: $input) {
id
uid
name
size
type
extension
shared
createdAt
}
}
`;
// Using multiple file inputs
const fileInputs = document.querySelectorAll('input[type="file"]');
const files = Array.from(fileInputs).map(input => input.files[0]).filter(Boolean);
const { data } = await uploadFiles({
variables: {
input: {
files: files,
projectId: "project_123", // or "my-project-slug"
companyId: "company_456" // or "my-company-slug"
}
}
});
Vanilla JavaScript
Single File Upload:
<!-- HTML -->
<input type="file" id="fileInput" />
<button onclick="uploadFile()">Upload File</button>
async function uploadFile() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) {
alert('Please select a file');
return;
}
// Create GraphQL mutation
const query = `
mutation UploadFile($input: UploadFileInput!) {
uploadFile(input: $input) {
id
name
size
type
extension
createdAt
}
}
`;
// Prepare form data
const formData = new FormData();
formData.append('operations', JSON.stringify({
query: query,
variables: {
input: {
file: null, // Will be replaced by file
projectId: "your_project_id", // or "your-project-slug"
companyId: "your_company_id" // or "your-company-slug"
}
}
}));
formData.append('map', JSON.stringify({
"0": ["variables.input.file"]
}));
formData.append('0', file);
try {
const response = await fetch('/graphql', {
method: 'POST',
body: formData,
headers: {
// Don't set Content-Type - let browser set it with boundary
'Authorization': 'Bearer your_auth_token'
}
});
const result = await response.json();
if (result.errors) {
console.error('Upload failed:', result.errors);
alert('Upload failed: ' + result.errors[0].message);
} else {
console.log('Upload successful:', result.data.uploadFile);
alert('File uploaded successfully!');
}
} catch (error) {
console.error('Network error:', error);
alert('Network error during upload');
}
}
Multiple Files Upload:
<!-- HTML -->
<input type="file" id="filesInput" multiple />
<button onclick="uploadFiles()">Upload Files</button>
@@CB##b45e342a-50d3-4711-a7ba-9b67c67cc2f5##CB@@javascript
async function uploadFiles() {
const filesInput = document.getElementById('filesInput');
const files = Array.from(filesInput.files);
if (files.length === 0) {
alert('Please select files');
return;
}
if (files.length > 10) {
alert('Maximum 10 files allowed');
return;
}
const query = `
mutation UploadFiles($input: UploadFilesInput!) {
uploadFiles(input: $input) {
id
name
size
type
extension
createdAt
}
}
`;
const formData = new FormData();
// Create file placeholders for variables
const fileVariables = files.map((_, index) => null);
formData.append('operations', JSON.stringify({
query: query,
variables: {
input: {
files: fileVariables,
projectId: "your_project_id", // or "your-project-slug"
companyId: "your_company_id" // or "your-company-slug"
}
}
}));
// Create map for file replacements
const map = {};
files.forEach((_, index) => {
map[index.toString()] = [`variables.input.files.${index}`];
});
formData.append('map', JSON.stringify(map));
// Append actual files
files.forEach((file, index) => {
formData.append(index.toString(), file);
});
try {
const response = await fetch('/graphql', {
method: 'POST',
body: formData,
headers: {
'Authorization': 'Bearer your_auth_token'
}
});
const result = await response.json();
if (result.errors) {
console.error('Upload failed:', result.errors);
alert('Upload failed: ' + result.errors[0].message);
} else {
console.log('Upload successful:', result.data.uploadFiles);
alert(`${result.data.uploadFiles.length} ファイルが正常にアップロードされました!`);
}
} catch (error) {
console.error('Network error:', error);
alert('Network error during upload');
}
}
@@CB##eb1a5246-66db-4551-beff-8cd55f7532ea##CB@@bash
# Single file upload with cURL
curl -X POST \
-H "Authorization: Bearer your_auth_token" \
-F 'operations={"query":"mutation UploadFile($input: UploadFileInput!) { uploadFile(input: $input) { id name size type extension createdAt } }","variables":{"input":{"file":null,"projectId":"your_project_id","companyId":"your_company_id"}}}' \
-F 'map={"0":["variables.input.file"]}' \
-F '0=@/path/to/your/file.jpg' \
https://your-api.com/graphql
REST API Upload
Use this method for files larger than 256MB (up to 4.8GB). This approach uses a three-step process: request upload credentials, upload to storage, then register the file in the database.
Prerequisites:
- Python 3.x installed
requests
library installed:pip install requests
- A valid X-Bloo-Token-ID and X-Bloo-Token-Secret for Blue API authentication
- The file to upload (e.g., test.jpg) in the same directory as the script
This method covers two scenarios:
- Uploading to the "File Tab"
- Uploading to the "Todo File Custom Field"
Configuration
Define these constants at the top of your script:
FILENAME = "test.jpg"
TOKEN_ID = "YOUR_TOKEN_ID"
TOKEN_SECRET = "YOUR_TOKEN_SECRET"
COMPANY_ID = "YOUR_COMPANY_ID_OR_SLUG"
PROJECT_ID = "YOUR_PROJECT_ID_OR_SLUG"
BASE_URL = "https://api.blue.cc"\
This is diagram that shows the flow of the upload process:
Uploading to File Tab
::code-group
import requests
import json
import os
# Configuration
FILENAME = "test.jpg"
TOKEN_ID = "YOUR_TOKEN_ID"
TOKEN_SECRET = "YOUR_TOKEN_SECRET"
COMPANY_ID = "YOUR_COMPANY_ID_OR_SLUG"
PROJECT_ID = "YOUR_PROJECT_ID_OR_SLUG"
BASE_URL = "https://api.blue.cc"
# Headers for Blue API
HEADERS = {
"X-Bloo-Token-ID": TOKEN_ID,
"X-Bloo-Token-Secret": TOKEN_SECRET,
"X-Bloo-Company-ID": COMPANY_ID,
"X-Bloo-Project-ID": PROJECT_ID,
}
# Step 1: Get upload credentials
def get_upload_credentials():
url = f"{BASE_URL}/uploads?filename={FILENAME}"
response = requests.get(url, headers=HEADERS)
if response.status_code != 200:
raise Exception(f"Failed to fetch upload credentials: {response.status_code} - {response.text}")
return response.json()
# Step 2: Upload file to S3
def upload_to_s3(credentials):
s3_url = credentials["url"]
fields = credentials["fields"]
files = {
"acl": (None, fields["acl"]),
"Content-Disposition": (None, fields["Content-Disposition"]),
"Key": (None, fields["Key"]),
"X-Key": (None, fields["X-Key"]),
"Content-Type": (None, fields["Content-Type"]),
"bucket": (None, fields["bucket"]),
"X-Amz-Algorithm": (None, fields["X-Amz-Algorithm"]),
"X-Amz-Credential": (None, fields["X-Amz-Credential"]),
"X-Amz-Date": (None, fields["X-Amz-Date"]),
"Policy": (None, fields["Policy"]),
"X-Amz-Signature": (None, fields["X-Amz-Signature"]),
"file": (FILENAME, open(FILENAME, "rb"), fields["Content-Type"])
}
response = requests.post(s3_url, files=files)
if response.status_code != 204:
raise Exception(f"S3 upload failed: {response.status_code} - {response.text}")
print("S3 upload successful")
# Step 3: Register file with Blue
def register_file(file_uid):
graphql_url = f"{BASE_URL}/graphql"
headers = HEADERS.copy()
headers["Content-Type"] = "application/json"
query = """
mutation CreateFile($uid: String!, $name: String!, $type: String!, $extension: String!, $size: Float!, $projectId: String!, $companyId: String!) {
createFile(input: {uid: $uid, name: $name, type: $type, size: $size, extension: $extension, projectId: $projectId, companyId: $companyId}) {
id
uid
name
__typename
}
}
"""
variables = {
"uid": file_uid,
"name": FILENAME,
"type": "image/jpeg",
"extension": "jpg",
"size": float(os.path.getsize(FILENAME)), # Dynamic file size
"projectId": PROJECT_ID,
"companyId": COMPANY_ID
}
payload = {
"operationName": "CreateFile",
"query": query,
"variables": variables
}
response = requests.post(graphql_url, headers=headers, json=payload)
if response.status_code != 200:
raise Exception(f"GraphQL registration failed: {response.status_code} - {response.text}")
print("File registration successful:", response.json())
# Main execution
def main():
try:
if not os.path.exists(FILENAME):
raise Exception(f"File '{FILENAME}' not found")
# Step 1: Fetch credentials
credentials = get_upload_credentials()
print("Upload credentials fetched:", credentials)
# Step 2: Upload to S3
upload_to_s3(credentials)
# Step 3: Register file
file_uid = credentials["fields"]["Key"].split("/")[0]
register_file(file_uid)
print("Upload completed successfully!")
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()
::
Steps Explained
Step 1: Request Upload Credentials
- Send a GET request to
https://api.blue.cc/uploads?filename=test.jpg
- Returns S3 credentials in JSON format
Sample Response:
@@CB##66c5ae64-471a-4bfd-a9a0-74211a2d23e1##CB@@
Step 2: Upload File to S3
- Uses the
files@@CODE##37f90349-e284-452e-947d-54c963efd330##CODE@@requests.post
to send a multipart/form-data request to the S3 URL - Ensures all fields match the policy exactly, avoiding curl's formatting issues
Step 3: Register File Metadata
- Sends a GraphQL mutation to
https://api.blue.cc/graphql
- Dynamically calculates file size with
os.path.getsize@@CODE##3c157b50-6b20-4eea-9701-de0674b499c4##CODE@@todoId
and `customFieldId@@CODE##16127308-ae8d-4651-aa2b-064837aa0b0b##CODE@@``json
{
"data": {
"createTodoCustomFieldFile": true
}
}